Skip to content

Commit f68a4d7

Browse files
committed
feat: migrate tslint to eslint
5 ESLint rules behave differently from their TSLint counterparts: * @typescript-eslint/no-unused-expressions: - The TSLint optional config "allow-new" is the default ESLint behavior and will no longer be ignored. * eqeqeq: - Option "smart" allows for comparing two literal values, evaluating the value of typeof and null comparisons. * no-invalid-this: - Functions in methods will no longer be ignored. * no-underscore-dangle: - Leading and trailing underscores (_) on identifiers will now be ignored. * prefer-arrow/prefer-arrow-functions: - ESLint does not support allowing standalone function declarations. - ESLint does not support allowing named functions defined with the function keyword.
1 parent ef2590c commit f68a4d7

31 files changed

+836
-141
lines changed

.eslintrc.js

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
module.exports = {
2+
env: {
3+
node: true,
4+
es6: true,
5+
},
6+
extends: ['plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'prettier', 'prettier/@typescript-eslint'],
7+
parser: '@typescript-eslint/parser',
8+
parserOptions: {
9+
project: ['./packages/**/tsconfig.json', './tests/tsconfig.json'],
10+
sourceType: 'module',
11+
},
12+
plugins: ['@typescript-eslint'],
13+
rules: {
14+
'@typescript-eslint/adjacent-overload-signatures': 'error',
15+
'@typescript-eslint/array-type': [
16+
'error',
17+
{
18+
default: 'array-simple',
19+
},
20+
],
21+
'@typescript-eslint/await-thenable': 'error',
22+
'@typescript-eslint/ban-ts-comment': 'error',
23+
'@typescript-eslint/ban-types': 'off',
24+
'@typescript-eslint/consistent-type-assertions': 'error',
25+
'@typescript-eslint/dot-notation': 'error',
26+
'@typescript-eslint/explicit-module-boundary-types': 'off',
27+
'@typescript-eslint/member-delimiter-style': [
28+
'off',
29+
{
30+
multiline: {
31+
delimiter: 'none',
32+
requireLast: true,
33+
},
34+
singleline: {
35+
delimiter: 'semi',
36+
requireLast: false,
37+
},
38+
},
39+
],
40+
'@typescript-eslint/member-ordering': 'off',
41+
'@typescript-eslint/naming-convention': 'off',
42+
'@typescript-eslint/no-array-constructor': 'error',
43+
'@typescript-eslint/no-empty-function': 'error',
44+
'@typescript-eslint/no-empty-interface': 'error',
45+
'@typescript-eslint/no-explicit-any': 'off',
46+
'@typescript-eslint/no-extra-non-null-assertion': 'error',
47+
'@typescript-eslint/no-extra-semi': 'error',
48+
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
49+
'@typescript-eslint/no-for-in-array': 'error',
50+
'@typescript-eslint/no-implied-eval': 'error',
51+
'@typescript-eslint/no-inferrable-types': 'error',
52+
'@typescript-eslint/no-misused-new': 'error',
53+
'@typescript-eslint/no-misused-promises': 'error',
54+
'@typescript-eslint/no-namespace': 'off',
55+
'@typescript-eslint/no-non-null-asserted-optional-chain': 'error',
56+
'@typescript-eslint/no-non-null-assertion': 'off',
57+
'@typescript-eslint/no-parameter-properties': 'off',
58+
'@typescript-eslint/no-this-alias': 'error',
59+
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
60+
'@typescript-eslint/no-unsafe-assignment': 'off',
61+
'@typescript-eslint/no-unsafe-call': 'off',
62+
'@typescript-eslint/no-unsafe-member-access': 'off',
63+
'@typescript-eslint/no-unsafe-return': 'off',
64+
'@typescript-eslint/no-unused-expressions': 'off',
65+
'@typescript-eslint/no-unused-vars': [
66+
'warn',
67+
{
68+
argsIgnorePattern: '^_',
69+
},
70+
],
71+
'@typescript-eslint/no-use-before-define': 'off',
72+
'@typescript-eslint/no-var-requires': 'off',
73+
'@typescript-eslint/prefer-as-const': 'error',
74+
'@typescript-eslint/prefer-for-of': 'error',
75+
'@typescript-eslint/prefer-function-type': 'error',
76+
'@typescript-eslint/prefer-namespace-keyword': 'error',
77+
'@typescript-eslint/prefer-regexp-exec': 'error',
78+
'@typescript-eslint/require-await': 'error',
79+
'@typescript-eslint/restrict-plus-operands': 'error',
80+
'@typescript-eslint/restrict-template-expressions': 'error',
81+
'@typescript-eslint/semi': ['off', null],
82+
'@typescript-eslint/triple-slash-reference': [
83+
'off',
84+
{
85+
path: 'always',
86+
types: 'prefer-import',
87+
lib: 'always',
88+
},
89+
],
90+
'@typescript-eslint/unbound-method': 'error',
91+
'@typescript-eslint/unified-signatures': 'error',
92+
'arrow-parens': ['off', 'always'],
93+
complexity: 'off',
94+
'constructor-super': 'error',
95+
eqeqeq: ['error', 'smart'],
96+
'guard-for-in': 'error',
97+
'id-match': 'error',
98+
'max-classes-per-file': 'off',
99+
'new-parens': 'error',
100+
'no-array-constructor': 'off',
101+
'no-bitwise': 'error',
102+
'no-caller': 'error',
103+
'no-cond-assign': 'error',
104+
'no-console': 'error',
105+
'no-debugger': 'error',
106+
'no-empty': 'error',
107+
'no-empty-function': 'off',
108+
'no-eval': 'error',
109+
'no-fallthrough': 'off',
110+
'no-invalid-this': 'off',
111+
'no-new-wrappers': 'error',
112+
'no-shadow': [
113+
'off',
114+
{
115+
hoist: 'all',
116+
},
117+
],
118+
'no-throw-literal': 'error',
119+
'no-trailing-spaces': 'error',
120+
'no-undef-init': 'error',
121+
'no-underscore-dangle': 'error',
122+
'no-unsafe-finally': 'error',
123+
'no-unused-labels': 'error',
124+
'no-unused-vars': 'off',
125+
'no-var': 'error',
126+
'object-shorthand': 'error',
127+
'one-var': ['error', 'never'],
128+
'prefer-const': 'error',
129+
radix: 'error',
130+
'require-await': 'off',
131+
'spaced-comment': [
132+
'error',
133+
'always',
134+
{
135+
markers: ['/'],
136+
},
137+
],
138+
'use-isnan': 'error',
139+
'valid-typeof': 'off',
140+
},
141+
overrides: [
142+
{
143+
files: './packages/runtime/src/decorators/*.ts',
144+
rules: {
145+
'@typescript-eslint/no-unused-vars': 'off',
146+
},
147+
},
148+
{
149+
files: './tests/**/*.ts',
150+
rules: {
151+
'@typescript-eslint/no-unused-vars': 'off',
152+
'@typescript-eslint/no-floating-promises': 'off',
153+
'@typescript-eslint/restrict-template-expressions': 'off',
154+
},
155+
},
156+
],
157+
};

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2-
"tslint.autoFixOnSave": true,
2+
"editor.codeActionsOnSave": {
3+
"eslint.autoFixOnSave": true
4+
},
35
"typescript.tsdk": "node_modules/typescript/lib"
46
}

package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,22 @@
3737
"prepare": "yarn build",
3838
"preversion": "yarn test",
3939
"test": "yarn --cwd tests test",
40-
"lint": "tslint ./packages/**/src/**/*.ts ./tests/**/*.ts",
40+
"lint": "eslint ./packages/**/src/**/*.ts ./tests/**/*.ts",
41+
"lint:fix": "npm run lint -- --fix",
4142
"pre-commit": "yarn lint && yarn test",
4243
"watch": "lerna run watch --parallel",
4344
"lerna": "lerna"
4445
},
4546
"dependencies": {},
4647
"devDependencies": {
47-
"lerna": "^3.20.2",
48+
"@typescript-eslint/eslint-plugin": "^4.0.1",
49+
"@typescript-eslint/parser": "^4.0.1",
50+
"eslint": "^7.8.1",
51+
"eslint-config-prettier": "^6.11.0",
4852
"husky": "^3.0.2",
49-
"prettier": "^2.0.5",
53+
"lerna": "^3.20.2",
5054
"lint-staged": "9.2.3",
51-
"tslint": "^6.1.1",
55+
"prettier": "^2.0.5",
5256
"typescript": "^3.9.2"
5357
},
5458
"repository": {

packages/cli/src/cli.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ const getConfig = async (configPath = 'tsoa.json'): Promise<Config> => {
5959
if (err.code === 'MODULE_NOT_FOUND') {
6060
throw Error(`No config file found at '${configPath}'`);
6161
} else if (err.name === 'SyntaxError') {
62-
// tslint:disable-next-line:no-console
62+
// eslint-disable-next-line no-console
6363
console.error(err);
6464
throw Error(`Invalid JSON syntax in config at '${configPath}': ${err.message}`);
6565
} else {
66-
// tslint:disable-next-line:no-console
66+
// eslint-disable-next-line no-console
6767
console.error(err);
6868
throw Error(`Unhandled error encountered loading '${configPath}': ${err.message}`);
6969
}
@@ -305,7 +305,7 @@ async function SpecGenerator(args: SwaggerArgs) {
305305

306306
await generateSpec(swaggerConfig, compilerOptions, config.ignore);
307307
} catch (err) {
308-
// tslint:disable-next-line:no-console
308+
// eslint-disable-next-line no-console
309309
console.error('Generate swagger error.\n', err);
310310
process.exit(1);
311311
}
@@ -323,7 +323,7 @@ async function routeGenerator(args: ConfigArgs) {
323323

324324
await generateRoutes(routesConfig, compilerOptions, config.ignore);
325325
} catch (err) {
326-
// tslint:disable-next-line:no-console
326+
// eslint-disable-next-line no-console
327327
console.error('Generate routes error.\n', err);
328328
process.exit(1);
329329
}
@@ -353,7 +353,7 @@ export async function generateSpecAndRoutes(args: SwaggerArgs) {
353353

354354
return await Promise.all([generateRoutes(routesConfig, compilerOptions, config.ignore, metadata), generateSpec(swaggerConfig, compilerOptions, config.ignore, metadata)]);
355355
} catch (err) {
356-
// tslint:disable-next-line:no-console
356+
// eslint-disable-next-line no-console
357357
console.error('Generate routes error.\n', err);
358358
process.exit(1);
359359
throw err;

packages/cli/src/metadataGeneration/exceptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class GenerateMetadataError extends Error {
55
constructor(message?: string, node?: Node, onlyCurrent = false) {
66
super(message);
77
if (node) {
8-
this.message = `${message}\n${prettyLocationOfNode(node)}\n${prettyTroubleCause(node, onlyCurrent)}`;
8+
this.message = `${message!}\n${prettyLocationOfNode(node)}\n${prettyTroubleCause(node, onlyCurrent)}`;
99
}
1010
}
1111
}

packages/cli/src/metadataGeneration/initializer-value.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const getInitializerValue = (initializer?: ts.Expression, typeChecker?: t
66
return;
77
}
88

9-
switch (initializer.kind as ts.SyntaxKind) {
9+
switch (initializer.kind) {
1010
case ts.SyntaxKind.ArrayLiteralExpression:
1111
const arrayLiteral = initializer as ts.ArrayLiteralExpression;
1212
return arrayLiteral.elements.map(element => getInitializerValue(element, typeChecker));

packages/cli/src/metadataGeneration/metadataGenerator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class MetadataGenerator {
1313
private referenceTypeMap: Tsoa.ReferenceTypeMap = {};
1414
private circularDependencyResolvers = new Array<(referenceTypes: Tsoa.ReferenceTypeMap) => void>();
1515

16-
public IsExportedNode(node: ts.Node) {
16+
public IsExportedNode(_node: ts.Node) {
1717
return true;
1818
}
1919

@@ -37,7 +37,7 @@ export class MetadataGenerator {
3737
};
3838
}
3939

40-
private setProgramToDynamicControllersFiles(controllers) {
40+
private setProgramToDynamicControllersFiles(controllers: string[]) {
4141
const allGlobFiles = importClassesFromDirectories(controllers);
4242
if (allGlobFiles.length === 0) {
4343
throw new GenerateMetadataError(`[${controllers.join(', ')}] globs found 0 controllers.`);
@@ -69,7 +69,7 @@ export class MetadataGenerator {
6969
* the same kind (`ModuleDeclaration`). In order to figure out whether it's one or the other,
7070
* we check the node flags. They tell us whether it is a namespace or not.
7171
*/
72-
// tslint:disable-next-line:no-bitwise
72+
// eslint-disable-next-line no-bitwise
7373
if ((node.flags & ts.NodeFlags.Namespace) === 0 && node.body && ts.isModuleBlock(node.body)) {
7474
node.body.statements.forEach(statement => {
7575
this.nodes.push(statement);

packages/cli/src/metadataGeneration/methodGenerator.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class MethodGenerator {
7474
} catch (e) {
7575
const methodId = this.node.name as ts.Identifier;
7676
const controllerId = (this.node.parent as ts.ClassDeclaration).name as ts.Identifier;
77-
throw new GenerateMetadataError(`${e.message} \n in '${controllerId.text}.${methodId.text}'`);
77+
throw new GenerateMetadataError(`${String(e.message)} \n in '${controllerId.text}.${methodId.text}'`);
7878
}
7979
})
8080
.filter((p): p is Tsoa.Parameter => p !== null);
@@ -112,7 +112,7 @@ export class MethodGenerator {
112112
return;
113113
}
114114
if (pathDecorators.length > 1) {
115-
throw new GenerateMetadataError(`Only one path decorator in '${this.getCurrentLocation}' method, Found: ${pathDecorators.map(d => d.text).join(', ')}`);
115+
throw new GenerateMetadataError(`Only one path decorator in '${this.getCurrentLocation()}' method, Found: ${pathDecorators.map(d => d.text).join(', ')}`);
116116
}
117117

118118
const decorator = pathDecorators[0];
@@ -158,7 +158,7 @@ export class MethodGenerator {
158158
};
159159
}
160160
if (decorators.length > 1) {
161-
throw new GenerateMetadataError(`Only one SuccessResponse decorator allowed in '${this.getCurrentLocation}' method.`);
161+
throw new GenerateMetadataError(`Only one SuccessResponse decorator allowed in '${this.getCurrentLocation()}' method.`);
162162
}
163163

164164
const [name, description] = getDecoratorValues(decorators[0], this.current.typeChecker);
@@ -196,7 +196,7 @@ export class MethodGenerator {
196196
return false;
197197
}
198198
if (depDecorators.length > 1) {
199-
throw new GenerateMetadataError(`Only one Deprecated decorator allowed in '${this.getCurrentLocation}' method.`);
199+
throw new GenerateMetadataError(`Only one Deprecated decorator allowed in '${this.getCurrentLocation()}' method.`);
200200
}
201201

202202
return true;
@@ -208,7 +208,7 @@ export class MethodGenerator {
208208
return undefined;
209209
}
210210
if (opDecorators.length > 1) {
211-
throw new GenerateMetadataError(`Only one OperationId decorator allowed in '${this.getCurrentLocation}' method.`);
211+
throw new GenerateMetadataError(`Only one OperationId decorator allowed in '${this.getCurrentLocation()}' method.`);
212212
}
213213

214214
const values = getDecoratorValues(opDecorators[0], this.current.typeChecker);
@@ -221,7 +221,7 @@ export class MethodGenerator {
221221
return this.parentTags;
222222
}
223223
if (tagsDecorators.length > 1) {
224-
throw new GenerateMetadataError(`Only one Tags decorator allowed in '${this.getCurrentLocation}' method.`);
224+
throw new GenerateMetadataError(`Only one Tags decorator allowed in '${this.getCurrentLocation()}' method.`);
225225
}
226226

227227
const tags = getDecoratorValues(tagsDecorators[0], this.current.typeChecker);
@@ -236,11 +236,11 @@ export class MethodGenerator {
236236
const securityDecorators = this.getDecoratorsByIdentifier(this.node, 'Security');
237237

238238
if (noSecurityDecorators?.length > 1) {
239-
throw new GenerateMetadataError(`Only one NoSecurity decorator allowed in '${this.getCurrentLocation}' method.`);
239+
throw new GenerateMetadataError(`Only one NoSecurity decorator allowed in '${this.getCurrentLocation()}' method.`);
240240
}
241241

242242
if (noSecurityDecorators?.length && securityDecorators?.length) {
243-
throw new GenerateMetadataError(`NoSecurity decorator cannot be used in conjunction with Security decorator in '${this.getCurrentLocation}' method.`);
243+
throw new GenerateMetadataError(`NoSecurity decorator cannot be used in conjunction with Security decorator in '${this.getCurrentLocation()}' method.`);
244244
}
245245

246246
if (noSecurityDecorators?.length) {
@@ -265,7 +265,7 @@ export class MethodGenerator {
265265
}
266266

267267
if (hiddenDecorators.length > 1) {
268-
throw new GenerateMetadataError(`Only one Hidden decorator allowed in '${this.getCurrentLocation}' method.`);
268+
throw new GenerateMetadataError(`Only one Hidden decorator allowed in '${this.getCurrentLocation()}' method.`);
269269
}
270270

271271
return true;

0 commit comments

Comments
 (0)