diff --git a/src/index.ts b/src/index.ts index bb011e5b..1cd0d2a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,6 +34,7 @@ import { idFlagSuggestions } from './rules/id-flag-suggestions'; import { noIdFlags } from './rules/migration/no-id-flags'; import { noFilepathFlags } from './rules/migration/no-filepath-flags'; import { noNumberFlags } from './rules/migration/no-number-flags'; +import { noSplitExamples } from './rules/no-split-examples'; import { noUsernameProperties } from './rules/migration/no-username-properties'; import { noUnnecessaryProperties } from './rules/no-unnecessary-properties'; @@ -57,6 +58,7 @@ const recommended = { 'sf-plugin/no-oclif-flags-command-import': 'error', 'sf-plugin/read-only-properties': 'warn', 'sf-plugin/id-flag-suggestions': 'warn', + 'sf-plugin/no-split-examples': 'error', 'sf-plugin/no-unnecessary-properties': 'warn', }, }; @@ -113,6 +115,7 @@ export = { 'no-id-flags': noIdFlags, 'no-filepath-flags': noFilepathFlags, 'no-number-flags': noNumberFlags, + 'no-split-examples': noSplitExamples, 'no-username-properties': noUsernameProperties, 'no-unnecessary-properties': noUnnecessaryProperties, }, diff --git a/src/rules/dash-o.ts b/src/rules/dash-o.ts index 4e2b9bf4..abc9759d 100644 --- a/src/rules/dash-o.ts +++ b/src/rules/dash-o.ts @@ -32,7 +32,6 @@ export const dashO = ESLintUtils.RuleCreator.withoutDocs({ ancestorsContainsSfCommand(context.getAncestors()) && node.value?.type === AST_NODE_TYPES.CallExpression && node.value.callee?.type === AST_NODE_TYPES.MemberExpression && - node.value.callee?.type === AST_NODE_TYPES.MemberExpression && node.value.callee.property.type === AST_NODE_TYPES.Identifier && !node.value.callee.property.name.toLowerCase().includes('org') && !node.value.callee.property.name.toLowerCase().includes('hub') && diff --git a/src/rules/no-split-examples.ts b/src/rules/no-split-examples.ts new file mode 100644 index 00000000..08e5354c --- /dev/null +++ b/src/rules/no-split-examples.ts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'; +import { extendsSfCommand, isInCommandDirectory } from '../shared/commands'; + +export const noSplitExamples = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + docs: { + description: 'Arrays of messags should use getMessages instead of getMessage followed by EOL splitting', + recommended: 'error', + }, + messages: { + message: 'use getMessages', + }, + type: 'problem', + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + return isInCommandDirectory(context) + ? { + PropertyDefinition(node): void { + if ( + node.static && + node.key.type === AST_NODE_TYPES.Identifier && + node.key.name === 'examples' && + node.parent.type === AST_NODE_TYPES.ClassBody && + node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration && + node.value.type === AST_NODE_TYPES.CallExpression && + node.value.callee.type === AST_NODE_TYPES.MemberExpression && + node.value.callee.object.type === AST_NODE_TYPES.CallExpression && + node.value.callee.object.callee.type === AST_NODE_TYPES.MemberExpression && + node.value.callee.object.callee.property.type === AST_NODE_TYPES.Identifier && + node.value.callee.object.callee.property.name === 'getMessage' && + node.value.callee.property.type === AST_NODE_TYPES.Identifier && + node.value.callee.property.name === 'split' && + extendsSfCommand(node.parent.parent) + ) { + const target = node.value; + const fixedText = context + .getSourceCode() + .getText(node.value) + .replace('getMessage', 'getMessages') + .replace(/\.split\(.*\)/, ''); + context.report({ + node: node.value.callee.property, + messageId: 'message', + fix: (fixer) => fixer.replaceText(target, fixedText), + }); + } + }, + } + : {}; + }, +}); diff --git a/test/rules/no-split-examples.test.ts b/test/rules/no-split-examples.test.ts new file mode 100644 index 00000000..98fa0975 --- /dev/null +++ b/test/rules/no-split-examples.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import path from 'path'; +import { ESLintUtils } from '@typescript-eslint/utils'; +import { noSplitExamples } from '../../src/rules/no-split-examples'; + +const ruleTester = new ESLintUtils.RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('noSplitExamples', noSplitExamples, { + valid: [ + { + name: 'correct examples with getMessages', + code: ` +export default class EnvCreateScratch extends SfCommand { + public static readonly examples = messages.getMessages('examples') +} +`, + }, + { + name: 'not an sf command', + code: ` +export default class EnvCreateScratch extends somethingElse { + public static readonly examples = message.getMessage('examples').split(EOL) +} +`, + }, + { + name: 'not in the commands folder', + filename: path.normalize('foo.ts'), + code: ` +export default class EnvCreateScratch extends SfCommand { + public static readonly examples = message.getMessage('examples').split(EOL) +} +`, + }, + ], + invalid: [ + { + name: 'getMessages with split', + filename: path.normalize('src/commands/foo.ts'), + errors: [ + { + messageId: 'message', + }, + ], + code: ` +export default class EnvCreateScratch extends SfCommand { + public static readonly examples = message.getMessage('examples').split(EOL); +} +`, + output: ` +export default class EnvCreateScratch extends SfCommand { + public static readonly examples = message.getMessages('examples'); +} +`, + }, + ], +});