Skip to content

Commit

Permalink
feat: migration of and suggestions for id flags
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Dec 21, 2022
1 parent 97f23cf commit bfbf6c4
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { noBuiltinFlags } from './rules/migration/no-builtin-flags';
import { dashO } from './rules/dash-o';
import { readOnlyProperties } from './rules/read-only-properties';
import { noTimeFlags } from './rules/migration/no-time-flags';
import { idFlagSuggestions } from './rules/id-flag-suggestions';

const recommended = {
plugins: ['sf-plugin'],
Expand All @@ -49,7 +50,8 @@ const recommended = {
'sf-plugin/no-json-flag': 'error',
'sf-plugin/run-matches-class-type': 'error',
'sf-plugin/no-oclif-flags-command-import': 'error',
'sf-plugin/read-only-properties': 'warning',
'sf-plugin/read-only-properties': 'warn',
'sf-plugin/id-flag-suggestions': 'warn',
},
};
export = {
Expand Down Expand Up @@ -99,5 +101,6 @@ export = {
'dash-o': dashO,
'read-only-properties': readOnlyProperties,
'no-time-flags': noTimeFlags,
'id-flag-suggestions': idFlagSuggestions,
},
};
95 changes: 95 additions & 0 deletions src/rules/id-flag-suggestions.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 { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
import { RuleFix, RuleFixer } from '@typescript-eslint/utils/dist/ts-eslint';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../shared/commands';
import { flagPropertyIsNamed, isFlag } from '../shared/flags';

export const idFlagSuggestions = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Create better salesforceId flags with length and startsWith properties',
recommended: 'warn',
},
hasSuggestions: true,
messages: {
message: 'Suggestion: salesforceId flags have additional properties to validate the Id. Consider using them.',
lengthSuggestion15: 'require the ID to be 15 characters',
lengthSuggestion18: 'require the ID to be 18 characters',
typeSuggestion: 'require the ID to start with a 3-character prefix',
},
type: 'suggestion',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return isInCommandDirectory(context)
? {
Property(node): void {
if (isFlag(node) && ancestorsContainsSfCommand(context.getAncestors())) {
if (
(node.key.type === AST_NODE_TYPES.Identifier || node.key.type === AST_NODE_TYPES.Literal) &&
node.value?.type === AST_NODE_TYPES.CallExpression &&
node.value.callee?.type === AST_NODE_TYPES.MemberExpression &&
node.value.callee.property?.type === AST_NODE_TYPES.Identifier &&
node.value.callee.property.name === 'salesforceId' &&
node.value.arguments?.[0]?.type === AST_NODE_TYPES.ObjectExpression
) {
const hasStartsWith = node.value.arguments[0].properties.some(
(property) => property.type === AST_NODE_TYPES.Property && flagPropertyIsNamed(property, 'startsWith')
);
const hasLength = node.value.arguments[0].properties.some(
(property) => property.type === AST_NODE_TYPES.Property && flagPropertyIsNamed(property, 'length')
);
if (!hasStartsWith || !hasLength) {
const existing = context.getSourceCode().getText(node);
const fixedStartsWith = existing.replace('salesforceId({', "salesforceId({startsWith: '000',");
const fixer15 = existing.replace('salesforceId({', 'salesforceId({length: 15,');
const fixer18 = existing.replace('salesforceId({', 'salesforceId({length: 18,');

context.report({
node: node.key,
messageId: 'message',
suggest: (!hasStartsWith
? [
{
// I think this is a TS problem in the utils
messageId: 'typeSuggestion' as keyof typeof idFlagSuggestions.meta.messages,
fix: (fixer: RuleFixer): RuleFix => {
return fixer.replaceText(node, fixedStartsWith);
},
},
]
: []
).concat(
!hasLength
? [
{
messageId: 'lengthSuggestion15',
fix: (fixer: RuleFixer): RuleFix => {
return fixer.replaceText(node, fixer15);
},
},
{
messageId: 'lengthSuggestion18',
fix: (fixer: RuleFixer): RuleFix => {
return fixer.replaceText(node, fixer18);
},
},
]
: []
),
});
}
}
}
},
}
: {};
},
});
51 changes: 51 additions & 0 deletions src/rules/migration/no-id-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 { ancestorsContainsSfCommand, isInCommandDirectory } from '../../shared/commands';
import { isFlag } from '../../shared/flags';

export const noIdFlags = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Change Id flag to salesforceId',
recommended: 'error',
},
messages: {
message: 'Id flags are not available on sfCommand. Use salesforceId instead',
},
type: 'problem',
schema: [],
fixable: 'code',
},
defaultOptions: [],
create(context) {
return isInCommandDirectory(context)
? {
Property(node): void {
if (isFlag(node) && ancestorsContainsSfCommand(context.getAncestors())) {
if (
(node.key.type === AST_NODE_TYPES.Identifier || node.key.type === AST_NODE_TYPES.Literal) &&
node.value?.type === AST_NODE_TYPES.CallExpression &&
node.value.callee?.type === AST_NODE_TYPES.MemberExpression &&
node.value.callee.property?.type === AST_NODE_TYPES.Identifier &&
node.value.callee.property.name === 'id'
) {
const toReplace = node.value.callee.property;
context.report({
node: node.value.callee.property,
messageId: 'message',
fix: (fixer) => {
return fixer.replaceText(toReplace, 'salesforceId');
},
});
}
}
},
}
: {};
},
});
2 changes: 1 addition & 1 deletion src/rules/migration/no-time-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const noTimeFlags = ESLintUtils.RuleCreator.withoutDocs({
Property(node): void {
if (isFlag(node) && ancestorsContainsSfCommand(context.getAncestors())) {
if (
node.key.type === AST_NODE_TYPES.Identifier &&
(node.key.type === AST_NODE_TYPES.Identifier || node.key.type === AST_NODE_TYPES.Literal) &&
node.value.type === AST_NODE_TYPES.CallExpression &&
node.value.callee.type === AST_NODE_TYPES.MemberExpression &&
node.value.callee.property.type === AST_NODE_TYPES.Identifier &&
Expand Down
163 changes: 163 additions & 0 deletions test/rules/id-flag-suggestions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* 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 { idFlagSuggestions } from '../../src/rules/id-flag-suggestions';

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

ruleTester.run('idFlagSuggestions', idFlagSuggestions, {
valid: [
{
name: 'salesforceId flag with length and startsWith',
filename: path.normalize('src/commands/foo.ts'),
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static flags = {
verbose: Flags.salesforceId({
length: 15,
startsWith: '00D',
}),
}
}
`,
},
],
invalid: [
{
name: 'neither',
filename: path.normalize('src/commands/foo.ts'),
errors: [
{
messageId: 'message',
suggestions: [
{
messageId: 'typeSuggestion',
output: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
id: Flags.salesforceId({startsWith: '000',
}),
}
}
`,
},
{
messageId: 'lengthSuggestion15',
output: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
id: Flags.salesforceId({length: 15,
}),
}
}
`,
},
{
messageId: 'lengthSuggestion18',
output: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
id: Flags.salesforceId({length: 18,
}),
}
}
`,
},
],
},
],
code: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
id: Flags.salesforceId({
}),
}
}
`,
},
{
name: 'only has length (uses literal)',
filename: path.normalize('src/commands/foo.ts'),
errors: [
{
messageId: 'message',
suggestions: [
{
messageId: 'typeSuggestion',
output: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
'some-flag': Flags.salesforceId({startsWith: '000',
length: 15,
}),
}
}
`,
},
],
},
],
code: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
'some-flag': Flags.salesforceId({
length: 15,
}),
}
}
`,
},
{
name: 'only has type',
filename: path.normalize('src/commands/foo.ts'),
errors: [
{
messageId: 'message',
suggestions: [
{
messageId: 'lengthSuggestion15',
output: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
foo: Flags.salesforceId({length: 15,
startsWith: '000',
}),
}
}
`,
},
{
messageId: 'lengthSuggestion18',
output: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
foo: Flags.salesforceId({length: 18,
startsWith: '000',
}),
}
}
`,
},
],
},
],
code: `
export default class EnvCreateScratch extends SfCommand<Foo> {
public static flags = {
foo: Flags.salesforceId({
startsWith: '000',
}),
}
}
`,
},
],
});
Loading

0 comments on commit bfbf6c4

Please sign in to comment.