diff --git a/packages/eslint-plugin/.vscode/launch.json b/packages/eslint-plugin/.vscode/launch.json new file mode 100644 index 000000000000..77bf32e163c6 --- /dev/null +++ b/packages/eslint-plugin/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Rule", + "program": "${workspaceFolder}/../../node_modules/jest/bin/jest.js", + "args": [ + "--runInBand", + "tests/rules/explicit-member-accessibility.test" + ] + } + ] +} diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index ebfbae27329d..c928fc7ed914 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -1,8 +1,15 @@ import { AST_NODE_TYPES, TSESTree, + AST_TOKEN_TYPES, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; +import { + ReportFixFunction, + RuleFixer, + RuleFix, +} from '@typescript-eslint/experimental-utils/dist/ts-eslint'; +import { Range } from '../../../typescript-estree/dist/ts-estree/ts-estree'; type AccessibilityLevel = | 'explicit' // require an accessor (including public) @@ -38,6 +45,7 @@ export default util.createRule({ // too opinionated to be recommended recommended: false, }, + fixable: 'code', messages: { missingAccessibility: 'Missing accessibility modifier on {{type}} {{name}}.', @@ -91,6 +99,7 @@ export default util.createRule({ nodeType: string, node: TSESTree.Node, nodeName: string, + fix: ReportFixFunction | null = null, ): void { context.report({ node: node, @@ -99,6 +108,7 @@ export default util.createRule({ type: nodeType, name: nodeName, }, + fix: fix, }); } @@ -140,6 +150,7 @@ export default util.createRule({ nodeType, methodDefinition, methodName, + getUnwantedPublicAccessibilityFixer(methodDefinition), ); } else if (check === 'explicit' && !methodDefinition.accessibility) { reportIssue( @@ -151,6 +162,53 @@ export default util.createRule({ } } + /** + * Creates a fixer that removes a "public" keyword with following spaces + */ + function getUnwantedPublicAccessibilityFixer( + node: + | TSESTree.MethodDefinition + | TSESTree.ClassProperty + | TSESTree.TSParameterProperty, + ): ReportFixFunction { + return function(fixer: RuleFixer): RuleFix { + const tokens = sourceCode.getTokens(node); + let rangeToRemove: Range; + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if ( + token.type === AST_TOKEN_TYPES.Keyword && + token.value === 'public' + ) { + const commensAfterPublicKeyword = sourceCode.getCommentsAfter( + token, + ); + if (commensAfterPublicKeyword.length) { + // public /* Hi there! */ static foo() + // ^^^^^^^ + rangeToRemove = [ + token.range[0], + commensAfterPublicKeyword[0].range[0], + ]; + break; + } else { + // public static foo() + // ^^^^^^^ + rangeToRemove = [token.range[0], tokens[i + 1].range[0]]; + break; + } + } + } + if (typeof rangeToRemove! === 'undefined') { + throw new Error( + `Couldn't define a range for "public" keyword that is to be deleted`, + ); + } + + return fixer.removeRange(rangeToRemove); + }; + } + /** * Checks if property has an accessibility modifier. * @param classProperty The node representing a ClassProperty. @@ -170,6 +228,7 @@ export default util.createRule({ nodeType, classProperty, propertyName, + getUnwantedPublicAccessibilityFixer(classProperty), ); } else if (propCheck === 'explicit' && !classProperty.accessibility) { reportIssue( @@ -217,6 +276,7 @@ export default util.createRule({ nodeType, node, nodeName, + getUnwantedPublicAccessibilityFixer(node), ); } break; diff --git a/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts b/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts index b5477c062a10..aa50954fef4e 100644 --- a/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts @@ -706,5 +706,164 @@ class Test { }, ], }, + { + filename: 'test.ts', + code: ` +class Test { + @public + public /*public*/constructor(private foo: string) {} +} + `, + options: [ + { + accessibility: 'no-public', + }, + ], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 3, + }, + ], + output: ` +class Test { + @public + /*public*/constructor(private foo: string) {} +} + `, + }, + { + filename: 'test.ts', + code: ` +class Test { + @public + public foo() {} +} + `, + options: [ + { + accessibility: 'no-public', + }, + ], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 3, + }, + ], + output: ` +class Test { + @public + foo() {} +} + `, + }, + + { + filename: 'test.ts', + code: ` +class Test { + @public + public foo; +} + `, + options: [ + { + accessibility: 'no-public', + }, + ], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 3, + }, + ], + output: ` +class Test { + @public + foo; +} + `, + }, + { + filename: 'test.ts', + code: ` +class Test { + public foo = ""; +} + `, + options: [ + { + accessibility: 'no-public', + }, + ], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 3, + }, + ], + output: ` +class Test { + foo = ""; +} + `, + }, + + { + filename: 'test.ts', + code: ` +class Test { + contructor(public /* Hi there */ readonly foo) +} + `, + options: [ + { + accessibility: 'no-public', + overrides: { parameterProperties: 'no-public' }, + }, + ], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 14, + }, + ], + output: ` +class Test { + contructor(/* Hi there */ readonly foo) +} + `, + }, + { + filename: 'test.ts', + code: ` +class Test { + contructor(public readonly foo: string) +} + `, + options: [ + { + accessibility: 'no-public', + }, + ], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 14, + }, + ], + output: ` +class Test { + contructor(readonly foo: string) +} + `, + }, ], });