Skip to content

Commit

Permalink
feat: autofix this.flags inside run method
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Dec 20, 2022
1 parent 2e39635 commit 94fab3b
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 35 deletions.
66 changes: 42 additions & 24 deletions src/rules/migration/noThisFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
*/
import { ESLintUtils } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { ancestorsContainsSfCommand, getRunMethod, getSfCommand, isInCommandDirectory } from '../../shared/commands';
import {
ancestorsContainsSfCommand,
getRunMethod,
getSfCommand,
isInCommandDirectory,
isRunMethod,
} from '../../shared/commands';
import { MemberExpressionIsThisDotFoo } from '../../shared/expressions';

export const noThisFlags = ESLintUtils.RuleCreator.withoutDocs({
Expand All @@ -32,7 +38,7 @@ export const noThisFlags = ESLintUtils.RuleCreator.withoutDocs({
? {
MemberExpression(node): void {
if (MemberExpressionIsThisDotFoo(node, 'flags') && ancestorsContainsSfCommand(context.getAncestors())) {
// it's ok if there's a this.org on the class...
// it's ok if there's a this.flags on the class...
const classAbove = getSfCommand(context.getAncestors());
const runMethod = getRunMethod(classAbove);

Expand Down Expand Up @@ -66,30 +72,42 @@ export const noThisFlags = ESLintUtils.RuleCreator.withoutDocs({
});
}
} 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');
},
// we have no this.flags.
// in run method, convert to parsed flags value.
if (context.getAncestors().some((b) => isRunMethod(b))) {
context.report({
node,
messageId: 'noThisFlags',
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>;`
);
});
} else {
// otherwise, your options are: 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>;`
);
},
},
],
});
}
}
}
},
Expand Down
22 changes: 11 additions & 11 deletions src/shared/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ export const isInCommandDirectory = (context: RuleContext<any, any>): boolean =>
return context.getPhysicalFilename().includes(`src${sep}commands${sep}`); // not an sfCommand
};

export const isRunMethod = (node: TSESTree.Node): boolean =>
node.type === AST_NODE_TYPES.MethodDefinition &&
node.kind === 'method' &&
node.computed === false &&
node.accessibility === 'public' &&
node.static === false &&
node.override === false &&
node.key.type === AST_NODE_TYPES.Identifier &&
node.key.name === 'run';

export const getRunMethod = (node: TSESTree.ClassDeclaration): TSESTree.ClassElement =>
node.body.body.find(
(b) =>
b.type === AST_NODE_TYPES.MethodDefinition &&
b.kind === 'method' &&
b.computed === false &&
b.accessibility === 'public' &&
b.static === false &&
b.override === false &&
b.key.type === AST_NODE_TYPES.Identifier &&
b.key.name === 'run'
);
node.body.body.find((b) => isRunMethod(b));
95 changes: 95 additions & 0 deletions test/rules/migration/noThisFlags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 { noThisFlags } from '../../../src/rules/migration/noThisFlags';

const ruleTester = new ESLintUtils.RuleTester({
parser: '@typescript-eslint/parser',
});

ruleTester.run('noThisFlags', noThisFlags, {
valid: [
{
name: 'Custom Type',
filename: path.normalize('src/commands/foo.ts'),
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static flags = {
foo: flags.string({ char: 'f', description: 'foo flag' }),
}
public async run(): Promise<ScratchCreateResponse> {
const {flags} = await this.parse(EnvCreateScratch)
console.log(flags.foo)
}
}
`,
},
{
name: 'Not in commands dir',
filename: path.normalize('foo.ts'),
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static flags = {
foo: flags.string({ char: 'f', description: 'foo flag' }),
}
public async run(): Promise<ScratchCreateResponse> {
const {flags} = await this.parse(EnvCreateScratch)
console.log(flags.foo)
}
}
`,
},
],
invalid: [
{
name: 'uses this.flags',
filename: path.normalize('src/commands/foo.ts'),
errors: [{ messageId: 'noThisFlags' }],
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static flags = {
foo: flags.string({ char: 'f', description: 'foo flag' }),
}
public async run(): Promise<ScratchCreateResponse> {
const {flags} = await this.parse(EnvCreateScratch)
}
public otherMethod() {
console.log(this.flags.foo)
}
}
`,
},
{
name: 'uses this.flags in run (autofix)',
filename: path.normalize('src/commands/foo.ts'),
errors: [{ messageId: 'noThisFlags' }],
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static flags = {
foo: flags.string({ char: 'f', description: 'foo flag' }),
}
public async run(): Promise<ScratchCreateResponse> {
const {flags} = await this.parse(EnvCreateScratch)
console.log(this.flags.foo)
}
}
`,
output: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static flags = {
foo: flags.string({ char: 'f', description: 'foo flag' }),
}
public async run(): Promise<ScratchCreateResponse> {
const {flags} = await this.parse(EnvCreateScratch)
console.log(flags.foo)
}
}
`,
},
],
});

0 comments on commit 94fab3b

Please sign in to comment.