Skip to content

Commit

Permalink
feat: make utils/TSESLint export typed classes instead of just types (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed May 16, 2019
1 parent 67537b8 commit 370ac72
Show file tree
Hide file tree
Showing 32 changed files with 479 additions and 343 deletions.
7 changes: 5 additions & 2 deletions .eslintrc.json
Expand Up @@ -5,11 +5,14 @@
"es6": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"comma-dangle": ["error", "always-multiline"],
"curly": ["error", "all"],
"no-dupe-class-members": "off",
"no-mixed-operators": "error",
"no-console": "off",
"no-dupe-class-members": "off",
Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-plugin-tslint/package.json
Expand Up @@ -27,14 +27,15 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@typescript-eslint/experimental-utils": "1.9.0",
"lodash.memoize": "^4.1.2"
},
"peerDependencies": {
"eslint": "^5.0.0",
"tslint": "^5.0.0"
},
"devDependencies": {
"@types/eslint": "^4.16.3",
"@types/json-schema": "^7.0.3",
"@types/lodash.memoize": "^4.1.4",
"@typescript-eslint/parser": "1.9.0"
}
Expand Down
159 changes: 4 additions & 155 deletions packages/eslint-plugin-tslint/src/index.ts
@@ -1,160 +1,9 @@
import { Rule } from 'eslint';
import memoize from 'lodash.memoize';
import { Configuration, RuleSeverity } from 'tslint';
import { Program } from 'typescript';
import { CustomLinter } from './custom-linter';
import { ParserServices } from '@typescript-eslint/typescript-estree';

//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------

type RawRuleConfig =
| null
| undefined
| boolean
| any[]
| {
severity?: RuleSeverity | 'warn' | 'none' | 'default';
options?: any;
};

interface RawRulesConfig {
[key: string]: RawRuleConfig;
}
import configRule from './rules/config';

/**
* Construct a configFile for TSLint
* Expose a single rule called "config", which will be accessed in the user's eslint config files
* via "tslint/config"
*/
const tslintConfig = memoize(
(
lintFile: string,
tslintRules: RawRulesConfig,
tslintRulesDirectory: string[],
) => {
if (lintFile != null) {
return Configuration.loadConfigurationFromPath(lintFile);
}
return Configuration.parseConfigFile({
rules: tslintRules || {},
rulesDirectory: tslintRulesDirectory || [],
});
},
(lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) =>
`${lintFile}_${Object.keys(tslintRules).join(',')}_${
tslintRulesDirectory.length
}`,
);

export const rules = {
/**
* Expose a single rule called "config", which will be accessed in the user's eslint config files
* via "tslint/config"
*/
config: {
meta: {
docs: {
description:
'Wraps a TSLint configuration and lints the whole source using TSLint',
category: 'TSLint',
},
schema: [
{
type: 'object',
properties: {
rules: {
type: 'object',
/**
* No fixed schema properties for rules, as this would be a permanently moving target
*/
additionalProperties: true,
},
rulesDirectory: {
type: 'array',
items: {
type: 'string',
},
},
lintFile: {
type: 'string',
},
},
additionalProperties: false,
},
],
},
create(context: Rule.RuleContext) {
const fileName = context.getFilename();
const sourceCode = context.getSourceCode().text;
const parserServices: ParserServices | undefined = context.parserServices;

/**
* The user needs to have configured "project" in their parserOptions
* for @typescript-eslint/parser
*/
if (!parserServices || !parserServices.program) {
throw new Error(
`You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`,
);
}

/**
* The TSLint rules configuration passed in by the user
*/
const {
rules: tslintRules,
rulesDirectory: tslintRulesDirectory,
lintFile,
} = context.options[0];

const program: Program = parserServices.program;

/**
* Create an instance of TSLint
* Lint the source code using the configured TSLint instance, and the rules which have been
* passed via the ESLint rule options for this rule (using "tslint/config")
*/
const tslintOptions = {
formatter: 'json',
fix: false,
};
const tslint = new CustomLinter(tslintOptions, program);
const configuration = tslintConfig(
lintFile,
tslintRules,
tslintRulesDirectory,
);
tslint.lint(fileName, sourceCode, configuration);

const result = tslint.getResult();

/**
* Format the TSLint results for ESLint
*/
if (result.failures && result.failures.length) {
result.failures.forEach(failure => {
const start = failure.getStartPosition().getLineAndCharacter();
const end = failure.getEndPosition().getLineAndCharacter();
context.report({
message: `${failure.getFailure()} (tslint:${failure.getRuleName()})`,
loc: {
start: {
line: start.line + 1,
column: start.character,
},
end: {
line: end.line + 1,
column: end.character,
},
},
});
});
}

/**
* Return an empty object for the ESLint rule
*/
return {};
},
},
config: configRule,
};
176 changes: 176 additions & 0 deletions packages/eslint-plugin-tslint/src/rules/config.ts
@@ -0,0 +1,176 @@
import {
ESLintUtils,
ParserServices,
} from '@typescript-eslint/experimental-utils';
import memoize from 'lodash.memoize';
import { Configuration, RuleSeverity } from 'tslint';
import { CustomLinter } from '../custom-linter';

// note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder
const version = require('../../package.json').version;

const createRule = ESLintUtils.RuleCreator(
() =>
`https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin-tslint/README.md`,
);
export type RawRulesConfig = Record<
string,
| null
| undefined
| boolean
| any[]
| {
severity?: RuleSeverity | 'warn' | 'none' | 'default';
options?: any;
}
>;

export type MessageIds = 'failure';
export type Options = [
{
rules?: RawRulesConfig;
rulesDirectory?: string[];
lintFile?: string;
}
];

/**
* Construct a configFile for TSLint
*/
const tslintConfig = memoize(
(
lintFile?: string,
tslintRules?: RawRulesConfig,
tslintRulesDirectory?: string[],
) => {
if (lintFile != null) {
return Configuration.loadConfigurationFromPath(lintFile);
}
return Configuration.parseConfigFile({
rules: tslintRules || {},
rulesDirectory: tslintRulesDirectory || [],
});
},
(lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) =>
`${lintFile}_${Object.keys(tslintRules).join(',')}_${
tslintRulesDirectory.length
}`,
);

export default createRule<Options, MessageIds>({
name: 'config',
meta: {
docs: {
description:
'Wraps a TSLint configuration and lints the whole source using TSLint',
category: 'TSLint' as any,
recommended: false,
},
type: 'problem',
messages: {
failure: '{{message}} (tslint:{{ruleName}})`',
},
schema: [
{
type: 'object',
properties: {
rules: {
type: 'object',
/**
* No fixed schema properties for rules, as this would be a permanently moving target
*/
additionalProperties: true,
},
rulesDirectory: {
type: 'array',
items: {
type: 'string',
},
},
lintFile: {
type: 'string',
},
},
additionalProperties: false,
},
],
},
defaultOptions: [] as any,
create(context) {
const fileName = context.getFilename();
const sourceCode = context.getSourceCode().text;
const parserServices: ParserServices | undefined = context.parserServices;

/**
* The user needs to have configured "project" in their parserOptions
* for @typescript-eslint/parser
*/
if (!parserServices || !parserServices.program) {
throw new Error(
`You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`,
);
}

/**
* The TSLint rules configuration passed in by the user
*/
const {
rules: tslintRules,
rulesDirectory: tslintRulesDirectory,
lintFile,
} = context.options[0];

const program = parserServices.program;

/**
* Create an instance of TSLint
* Lint the source code using the configured TSLint instance, and the rules which have been
* passed via the ESLint rule options for this rule (using "tslint/config")
*/
const tslintOptions = {
formatter: 'json',
fix: false,
};
const tslint = new CustomLinter(tslintOptions, program);
const configuration = tslintConfig(
lintFile,
tslintRules,
tslintRulesDirectory,
);
tslint.lint(fileName, sourceCode, configuration);

const result = tslint.getResult();

/**
* Format the TSLint results for ESLint
*/
if (result.failures && result.failures.length) {
result.failures.forEach(failure => {
const start = failure.getStartPosition().getLineAndCharacter();
const end = failure.getEndPosition().getLineAndCharacter();
context.report({
messageId: 'failure',
data: {
message: failure.getFailure(),
ruleName: failure.getRuleName(),
},
loc: {
start: {
line: start.line + 1,
column: start.character,
},
end: {
line: end.line + 1,
column: end.character,
},
},
});
});
}

/**
* Return an empty object for the ESLint rule
*/
return {};
},
});

0 comments on commit 370ac72

Please sign in to comment.