Skip to content

Commit

Permalink
feat: migration rules
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Dec 4, 2022
1 parent 2db9e7d commit c0cd575
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 63 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": ["TSES"]
}
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@
],
"repository": "salesforcecli/eslint-plugin-sf-plugin",
"dependencies": {
"@typescript-eslint/utils": "^5.44.0"
"@typescript-eslint/utils": "^5.45.0"
},
"devDependencies": {
"@salesforce/prettier-config": "^0.0.2",
"@types/eslint": "^8.4.10",
"@types/estree": "^0.0.52",
"@types/estree": "^1.0.0",
"@types/jest": "^28.1.7",
"@types/node": "^18.11.9",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"eslint": "^8.28.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.29.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-salesforce": "^0.1.6",
"eslint-config-salesforce-license": "^0.1.6",
Expand Down
16 changes: 15 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ 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 { noThisOrg } from './rules/migration/noThisOrg';
import { runMatchesClassType } from './rules/runMatchesClassType';

import { noDeprecatedProperties } from './rules/migration/noDeprecatedProperties';
import { shouldParseFlags } from './rules/migration/shouldParseFlags';
import { noThisFlags } from './rules/migration/noThisFlags';
import { getConnectionWithVersion } from './rules/getConnectionsWithVersion';
const recommended = {
plugins: ['sf-plugin'],
rules: {
Expand All @@ -36,6 +40,7 @@ const recommended = {
'sf-plugin/json-flag': 'error',
'sf-plugin/flag-min-max-default': 'warn',
'sf-plugin/run-matches-class-type': 'error',
'sf-plugin/get-connection-with-version': 'warn',
},
};
export = {
Expand All @@ -49,6 +54,10 @@ export = {
'sf-plugin/sfdx-flags-property': 'error',
'sf-plugin/use-sf-command-flags': 'error',
'sf-plugin/no-this-ux': 'error',
'sf-plugin/no-deprecated-properties': 'error',
'sf-plugin/should-parse-flags': 'error',
'sf-plugin/no-this-org': 'error',
'sf-plugin/no-this-flags': 'error',
},
},
},
Expand All @@ -69,5 +78,10 @@ export = {
'sfdx-flags-property': sfdxFlagsProperty,
'use-sf-command-flags': useSfCommandFlags,
'no-this-ux': noThisUx,
'no-deprecated-properties': noDeprecatedProperties,
'should-parse-flags': shouldParseFlags,
'no-this-org': noThisOrg,
'no-this-flags': noThisFlags,
'get-connection-with-version': getConnectionWithVersion,
},
};
46 changes: 46 additions & 0 deletions src/rules/getConnectionsWithVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 getConnectionWithVersion = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Calls to getConnection should pass in a version',
recommended: 'warn',
},
messages: {
addVersion: `getConnection should pass in a version, typically from the api-version flag,
even if that value may be undefined.
Otherwise, the org will default to its maximum version`,
},
type: 'problem',
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node): void {
if (
isInCommandDirectory(context) &&
node.type === AST_NODE_TYPES.CallExpression &&
node.arguments.length === 0 &&
node.callee?.type === AST_NODE_TYPES.MemberExpression &&
node.callee.property.type === AST_NODE_TYPES.Identifier &&
node.callee.property?.name === 'getConnection' &&
ancestorsContainsSfCommand(context.getAncestors())
) {
context.report({
node: node.callee.property,
messageId: 'addVersion',
});
}
},
};
},
});
52 changes: 52 additions & 0 deletions src/rules/migration/noDeprecatedProperties.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 { isInCommandDirectory, ancestorsContainsSfCommand } from '../../shared/commands';
export const noDeprecatedProperties = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Removes non-existent properties left over from SfdxCommand',
recommended: 'error',
},
messages: {
property: 'Class property {{property}} is not available on SfCommand and should be removed',
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return {
PropertyDefinition(node): void {
if (isInCommandDirectory(context) && ancestorsContainsSfCommand(context.getAncestors())) {
if (
node.key.type === 'Identifier' &&
[
'requiresUsername',
'supportUsername',
'supportsDevhubUsername',
'requiresDevhubUsername',
'varargs',
].includes(node.key.name)
) {
context.report({
node,
messageId: 'property',
data: {
property: node.key.name,
},
fix: (fixer) => {
return fixer.remove(node);
},
});
}
}
},
};
},
});
101 changes: 101 additions & 0 deletions src/rules/migration/noThisFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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, getRunMethod, getSfCommand, isInCommandDirectory } from '../../shared/commands';

export const noThisFlags = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Fix references to this.org (property on SfdxCommand)',
recommended: 'error',
},
messages: {
noThisFlags: 'SfCommand does not have a this.flags property. Make sure you parse the flag.',
useFlags: 'change this.flags to flags',
instanceProp: 'create a this.flags property on SfCommand',
setThisFlags: 'flags is defined on the class, but never set. Set it equal to the parsed flags property.',
},
hasSuggestions: true,
type: 'suggestion',
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.ThisExpression &&
node.property?.type === AST_NODE_TYPES.Identifier &&
node.property?.name === 'flags' &&
ancestorsContainsSfCommand(context.getAncestors())
) {
// it's ok if there's a this.org on the class...
const classAbove = getSfCommand(context.getAncestors());
const runMethod = getRunMethod(classAbove);

if (
classAbove &&
classAbove.body.body.find(
(b) =>
b.type === 'PropertyDefinition' &&
b.key.type === 'Identifier' &&
b.key.name === 'flags' &&
b.static === false
)
) {
// ...as long as it's been set in the run method
const flagsParse =
runMethod.type === 'MethodDefinition'
? runMethod.value.body.body.find(
(b) => b.type === 'VariableDeclaration' && context.getSourceCode().getText(b).includes('this.parse')
)
: undefined;
const source = context.getSourceCode().getText();
if (!source.includes('this.flags = ')) {
context.report({
node,
messageId: 'instanceProp',
fix: (fixer) => {
return fixer.insertTextAfter(flagsParse, 'this.flags = flags;');
},
});
}
} else {
// we have no this.flags. Make one, or use flags
context.report({
node,
messageId: 'noThisFlags',
suggest: [
{
messageId: 'useFlags',
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
fix: (fixer) => {
return fixer.replaceText(node, 'flags');
},
},
{
messageId: 'instanceProp',
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
fix: (fixer) => {
return fixer.insertTextBefore(
runMethod,
`private flags: Interfaces.InferredFlags<typeof ${classAbove.id.name}.flags>;`
);
},
},
],
});
}
}
},
};
},
});
94 changes: 94 additions & 0 deletions src/rules/migration/noThisOrg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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, getRunMethod, getSfCommand, isInCommandDirectory } from '../../shared/commands';

export const noThisOrg = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Fix references to this.org (property on SfdxCommand)',
recommended: 'error',
},
messages: {
noThisOrg: 'SfCommand does not have a this.org property. Make sure you parse the org flag.',
useFlags: "change this.org to flags['target-org']",
instanceProp: 'create a this.org property on SfCommand',
setThisOrg: 'this org is defined on the class, but never set. Set it equal to the org flag.',
},
hasSuggestions: true,
type: 'suggestion',
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.ThisExpression &&
node.property?.type === AST_NODE_TYPES.Identifier &&
node.property?.name === 'org' &&
ancestorsContainsSfCommand(context.getAncestors())
) {
// it's ok if there's a this.org on the class...
const classAbove = getSfCommand(context.getAncestors());
const runMethod = getRunMethod(classAbove);

if (
classAbove &&
classAbove.body.body.find(
(b) => b.type === 'PropertyDefinition' && b.key.type === 'Identifier' && b.key.name === 'org'
)
) {
// ...as long as it's been set in the run method
const flagsParse =
runMethod.type === 'MethodDefinition'
? runMethod.value.body.body.find(
(b) => b.type === 'VariableDeclaration' && context.getSourceCode().getText(b).includes('this.parse')
)
: undefined;
const source = context.getSourceCode().getText();
if (!source.includes('this.org = ')) {
context.report({
node,
messageId: 'instanceProp',
fix: (fixer) => {
return fixer.insertTextAfter(flagsParse, "this.org = flags['target-org'];");
},
});
}
} else {
// we have no this.flags. Make one, or use the parsed flags
context.report({
node,
messageId: 'noThisOrg',
suggest: [
{
messageId: 'useFlags',
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
fix: (fixer) => {
return fixer.replaceText(node, "flags['target-org']");
},
},
{
messageId: 'instanceProp',
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
fix: (fixer) => {
return fixer.insertTextBefore(runMethod, 'private org: Org;\r');
},
},
],
});
}
}
},
};
},
});
3 changes: 2 additions & 1 deletion src/rules/migration/sfdxFlagsProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
*/
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',
description: 'Change flag definitions to SfCommand version',
recommended: 'error',
},
messages: {
Expand Down
Loading

0 comments on commit c0cd575

Please sign in to comment.