Skip to content

Commit

Permalink
feat: sfdx migration helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Dec 2, 2022
1 parent 2abf8ef commit fea79bb
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 13 deletions.
46 changes: 34 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,47 @@ import { commandExamples } from './rules/commandExamples';
import { extractMessageCommand } from './rules/extractMessageCommand';
import { jsonFlag } from './rules/jsonFlag';
import { dashH } from './rules/dash-h';
import { noSfdxCommandImport } from './rules/migration/noSfdxCommandImport';
import { sfdxFlagsProperty } from './rules/migration/sfdxFlagsProperty';
import { useSfCommandFlags } from './rules/migration/useSfCommandFlags';
import { noThisUx } from './rules/migration/no-this-ux';
import { runMatchesClassType } from './rules/runMatchesClassType';

const recommended = {
plugins: ['sf-plugin'],
rules: {
'sf-plugin/command-summary': 'error',
'sf-plugin/command-example': 'warn',
'sf-plugin/no-duplicate-short-characters': 'error',
'sf-plugin/no-h-short-char': 'error',
'sf-plugin/flag-case': 'error',
'sf-plugin/flag-summary': 'error',
'sf-plugin/no-hardcoded-messages-flags': 'warn',
'sf-plugin/no-hardcoded-messages-commands': 'warn',
'sf-plugin/flag-cross-references': 'error',
'sf-plugin/json-flag': 'error',
'sf-plugin/flag-min-max-default': 'warn',
'sf-plugin/run-matches-class-type': 'error',
},
};
export = {
configs: {
recommended: {
recommended,
migration: {
plugins: ['sf-plugin'],
rules: {
'sf-plugin/command-summary': 'error',
'sf-plugin/command-example': 'warn',
'sf-plugin/no-duplicate-short-characters': 'error',
'sf-plugin/no-h-short-char': 'error',
'sf-plugin/flag-case': 'error',
'sf-plugin/flag-summary': 'error',
'sf-plugin/no-hardcoded-messages-flags': 'warn',
'sf-plugin/no-hardcoded-messages-commands': 'warn',
'sf-plugin/flag-cross-references': 'error',
'sf-plugin/json-flag': 'error',
'sf-plugin/flag-min-max-default': 'warn',
...recommended.rules,
'sf-plugin/no-sfdx-command-import': 'error',
'sf-plugin/sfdx-flags-property': 'error',
'sf-plugin/use-sf-command-flags': 'error',
'sf-plugin/no-this-ux': 'error',
},
},
},
rules: {
'no-h-short-char': dashH,
'no-duplicate-short-characters': noDuplicateShortCharacters,
'run-matches-class-type': runMatchesClassType,
'flag-case': flagCasing,
'flag-summary': flagSummary,
'no-hardcoded-messages-flags': extractMessageFlags,
Expand All @@ -47,5 +65,9 @@ export = {
'command-example': commandExamples,
'json-flag': jsonFlag,
'flag-min-max-default': flagMinMaxDefault,
'no-sfdx-command-import': noSfdxCommandImport,
'sfdx-flags-property': sfdxFlagsProperty,
'use-sf-command-flags': useSfCommandFlags,
'no-this-ux': noThisUx,
},
};
16 changes: 15 additions & 1 deletion src/rules/flagSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const flagSummary = ESLintUtils.RuleCreator.withoutDocs({
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
Expand All @@ -32,9 +33,22 @@ export const flagSummary = ESLintUtils.RuleCreator.withoutDocs({
(property) => property.type === 'Property' && flagPropertyIsNamed(property, 'summary')
)
) {
context.report({
// use the description as the summary if it exists
const descriptionProp = node.value.arguments[0].properties.find(
(property) => property.type === 'Property' && flagPropertyIsNamed(property, 'description')
);
const range = descriptionProp && 'key' in descriptionProp ? descriptionProp?.key.range : undefined;
return context.report({
node,
messageId: 'message',
...(range
? {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
fix: (fixer) => {
return fixer.replaceTextRange(range, 'summary');
},
}
: {}),
});
}
}
Expand Down
80 changes: 80 additions & 0 deletions src/rules/migration/no-this-ux.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 { ESLintUtils } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../../shared/commands';

const spinnerMigration = new Map([
['startSpinner', 'this.spinner.start'],
['stopSpinner', 'this.spinner.stop'],
]);

export const noThisUx = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'SfCommand does not have a ux property',
recommended: 'error',
},
messages: {
message: 'SfCommand does not have a ux property. Use methods from this like this.log() or this.table()',
spinner: 'SfCommand does not have a ux.spinner. Use this.spinner.start() or this.spinner.stop()',
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return {
MemberExpression(node): void {
if (
isInCommandDirectory(context) &&
node.type === AST_NODE_TYPES.MemberExpression &&
node.object?.type === AST_NODE_TYPES.MemberExpression &&
node.object?.object?.type === AST_NODE_TYPES.ThisExpression &&
node.object?.property?.type === AST_NODE_TYPES.Identifier &&
node.object?.property?.name === 'ux' &&
ancestorsContainsSfCommand(context.getAncestors())
) {
// spinner cases
if (node.property.type === 'Identifier' && spinnerMigration.has(node.property.name)) {
// all other this.ux cases
const toRemove = node;
const original = node.property.name;
context.report({
node,
messageId: 'spinner',
fix: (fixer) => {
return fixer.replaceText(toRemove, spinnerMigration.get(original));
},
});
} else if (node.property.type === 'Identifier' && node.property.name === 'logJson') {
// this.ux.logJson => this.styledJson
const toRemove = node;
context.report({
node,
messageId: 'spinner',
fix: (fixer) => {
return fixer.replaceText(toRemove, 'this.styledJSON');
},
});
} else {
// all other this.ux cases
const toRemove = node.object;
context.report({
node,
messageId: 'message',
fix: (fixer) => {
return fixer.replaceText(toRemove, 'this');
},
});
}
}
},
};
},
});
55 changes: 55 additions & 0 deletions src/rules/migration/noSfdxCommandImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { ESLintUtils } from '@typescript-eslint/utils';
import { isInCommandDirectory } from '../../shared/commands';
export const noSfdxCommandImport = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Change import to sfCommand',
recommended: 'error',
},
messages: {
import: 'Use SfCommand from sf-plugins-core',
superClass: 'Use SfCommand as the base class',
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return {
ClassDeclaration(node): void {
if (isInCommandDirectory(context)) {
if (node.superClass?.type === 'Identifier' && node.superClass.name === 'SfdxCommand') {
context.report({
node: node.superClass,
messageId: 'superClass',
fix: (fixer) => {
return fixer.replaceTextRange(node.superClass.range, 'SfCommand<unknown>');
},
});
}
}
},
ImportDeclaration(node): void {
// verify it extends SfCommand
if (isInCommandDirectory(context)) {
if (node.source.value === '@salesforce/command') {
context.report({
node,
messageId: 'import',
fix: (fixer) => {
return fixer.replaceText(node, "import {Flags, SfCommand} from '@salesforce/sf-plugins-core'");
},
});
}
}
},
};
},
});
56 changes: 56 additions & 0 deletions src/rules/migration/sfdxFlagsProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { ESLintUtils } from '@typescript-eslint/utils';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../../shared/commands';
export const sfdxFlagsProperty = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Change flag definitions to SfCommmand version',
recommended: 'error',
},
messages: {
flagsConfig: 'Use public readonly static flags = {',
flagsConfigType: 'The FlagsConfig type is not used by SfCommand',
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return {
PropertyDefinition(node): void {
if (isInCommandDirectory(context) && ancestorsContainsSfCommand(context.getAncestors())) {
if (node.key.type === 'Identifier' && node.key.name === 'flagsConfig') {
context.report({
node,
messageId: 'flagsConfig',
fix: (fixer) => {
return fixer.replaceTextRange(node.key.range, 'flags');
},
});
}
if (
node.key.type === 'Identifier' &&
node.typeAnnotation?.type === 'TSTypeAnnotation' &&
node.typeAnnotation.typeAnnotation?.type === 'TSTypeReference' &&
node.typeAnnotation.typeAnnotation.typeName?.type === 'Identifier' &&
node.typeAnnotation.typeAnnotation.typeName.name === 'FlagsConfig'
) {
context.report({
node,
messageId: 'flagsConfigType',
fix: (fixer) => {
return fixer.remove(node.typeAnnotation);
},
});
}
}
},
};
},
});
52 changes: 52 additions & 0 deletions src/rules/migration/useSfCommandFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { ESLintUtils } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../../shared/commands';

export const useSfCommandFlags = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'use Flags export from sf-plugins-core',
recommended: 'error',
},
messages: {
message: 'for SfCommand, each flag definition should use "Flags", not "flags"',
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return {
Property(node): void {
if (
isInCommandDirectory(context) &&
node.type === AST_NODE_TYPES.Property &&
node.value?.type === 'CallExpression' &&
node.value?.callee?.type === 'MemberExpression' &&
node.value?.callee?.object?.type === 'Identifier' &&
node.value?.callee?.object?.name === 'flags' &&
ancestorsContainsSfCommand(context.getAncestors())
) {
const range = node.value.callee.object.range;
context.report({
node,
messageId: 'message',
fix: (fixer) => {
// TS isn't using the type narrowing done above.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore, eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
return fixer.replaceTextRange(range, 'Flags');
},
});
}
},
};
},
});
Loading

0 comments on commit fea79bb

Please sign in to comment.