Skip to content

Commit

Permalink
feat: command example and summaries
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Jun 12, 2022
1 parent e8f1e47 commit 8461761
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 41 deletions.
37 changes: 37 additions & 0 deletions src/rules/commandExamples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { extendsSfCommand, getClassPropertyIdentifierName, isInCommandDirectory } from '../shared/commands';
export const commandExamples = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Ensure commands have a summary, description, and examples',
recommended: 'error',
},
messages: {
example: 'Commands should have an examples property',
},
type: 'problem',
schema: [],
},
defaultOptions: [],
create(context) {
return {
ClassDeclaration(node): void {
// verify it extends SfCommand
if (isInCommandDirectory(context) && extendsSfCommand(node)) {
if (!node.body.body.some((member) => getClassPropertyIdentifierName(member) === 'examples')) {
context.report({
node,
messageId: 'example',
});
}
}
},
};
},
});
37 changes: 37 additions & 0 deletions src/rules/commandSummary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { extendsSfCommand, getClassPropertyIdentifierName, isInCommandDirectory } from '../shared/commands';
export const commandSummary = ESLintUtils.RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Ensure commands have a summary',
recommended: 'error',
},
messages: {
summary: 'Commands should have a summary property',
},
type: 'problem',
schema: [],
},
defaultOptions: [],
create(context) {
return {
ClassDeclaration(node): void {
// verify it extends SfCommand
if (isInCommandDirectory(context) && extendsSfCommand(node)) {
if (!node.body.body.some((member) => getClassPropertyIdentifierName(member) === 'summary')) {
context.report({
node,
messageId: 'summary',
});
}
}
},
};
},
});
80 changes: 39 additions & 41 deletions src/rules/noDuplicateShortCharacters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
import { isFlagsStaticProperty } from '../shared/flags';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../shared/commands';
import { isFlagsStaticProperty, resolveFlagName } from '../shared/flags';

export const noDuplicateShortCharacters = ESLintUtils.RuleCreator.withoutDocs({
meta: {
Expand All @@ -24,49 +25,46 @@ export const noDuplicateShortCharacters = ESLintUtils.RuleCreator.withoutDocs({
return {
PropertyDefinition(node): void {
// is "public static flags" property
if (isFlagsStaticProperty(node)) {
if (
isInCommandDirectory(context) &&
node.value?.type === AST_NODE_TYPES.ObjectExpression &&
isFlagsStaticProperty(node) &&
ancestorsContainsSfCommand(context.getAncestors())
) {
const charFlagMap = new Map();
if (node.value.type === AST_NODE_TYPES.ObjectExpression) {
node.value.properties.forEach((flag) => {
// only if it has a char prop
if (
flag.type === 'Property' &&
flag.value.type === 'CallExpression' &&
flag.value.arguments?.[0]?.type === AST_NODE_TYPES.ObjectExpression &&
flag.value.arguments?.[0]?.properties.some(
(p) => p.type === 'Property' && p.key.type === AST_NODE_TYPES.Identifier && p.key.name === 'char'
)
) {
const charNode = flag.value.arguments[0].properties.find(
(p) => p.type === 'Property' && p.key.type === AST_NODE_TYPES.Identifier && p.key.name === 'char'
);
if (charNode.type === 'Property' && charNode.value.type === AST_NODE_TYPES.Literal) {
const char = charNode.value.raw;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const flagName =
flag.key.type === AST_NODE_TYPES.Identifier
? flag.key.name
: flag.key.type === AST_NODE_TYPES.Literal
? flag.key.value
: // those are the only types I've seen examples of
undefined;
if (!charFlagMap.has(char)) {
charFlagMap.set(char, flagName);
} else {
context.report({
node,
messageId: 'message',
data: {
flag1: flagName,
flag2: charFlagMap.get(char),
char,
},
});
}
node.value.properties.forEach((flag) => {
// only if it has a char prop
if (
flag.type === 'Property' &&
flag.value.type === 'CallExpression' &&
flag.value.arguments?.[0]?.type === AST_NODE_TYPES.ObjectExpression &&
flag.value.arguments?.[0]?.properties.some(
(p) => p.type === 'Property' && p.key.type === AST_NODE_TYPES.Identifier && p.key.name === 'char'
)
) {
const charNode = flag.value.arguments[0].properties.find(
(p) => p.type === 'Property' && p.key.type === AST_NODE_TYPES.Identifier && p.key.name === 'char'
);
if (charNode.type === 'Property' && charNode.value.type === AST_NODE_TYPES.Literal) {
const char = charNode.value.raw;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const flagName = resolveFlagName(flag);
if (!charFlagMap.has(char)) {
charFlagMap.set(char, flagName);
} else {
context.report({
node,
messageId: 'message',
data: {
flag1: flagName,
flag2: charFlagMap.get(char),
char,
},
});
}
}
});
}
}
});
}
},
};
Expand Down
61 changes: 61 additions & 0 deletions test/rules/commandExample.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 { commandExamples } from '../../src/rules/commandExamples';

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

ruleTester.run('commandExamples', commandExamples, {
valid: [
// example with different chars
{
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static readonly summary = 'foo'
public static readonly examples = 'baz'
}
`,
},
// not an sfCommand
{
code: `
export default class EnvCreateScratch extends somethingElse<ScratchCreateResponse> {
// stuff
}
`,
},
// violation but in wrong folder
{
filename: 'foo.ts',
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static readonly description = 'bar'
public static readonly summary = 'baz'
}
`,
},
],
invalid: [
{
filename: 'src/commands/foo.ts',
errors: [
{
messageId: 'example',
},
],
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static readonly description = 'bar'
public static readonly summary = 'baz'
}
`,
},
],
});
74 changes: 74 additions & 0 deletions test/rules/commandSummary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { commandSummary } from '../../src/rules/commandSummary';

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

ruleTester.run('commandSummary', commandSummary, {
valid: [
{
filename: 'src/commands/foo.ts',
code:
// example with different chars
`
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static readonly summary = 'foo'
public static readonly examples = 'baz'
}
`,
},
// not an sfCommand
{
filename: 'src/commands/foo.ts',
code: `
export default class EnvCreateScratch extends somethingElse<ScratchCreateResponse> {
// stuff
}
`,
},
// not an command class
{
filename: 'src/commands/foo.ts',
code: `
export abstract class StagedProgress<T> {
private dataForTheStatus: T;
private theStages: Stage;
}
`,
},
// not an command directory
{
filename: 'src/shared/.ts',
code: `
export abstract class StagedProgress<T> {
private dataForTheStatus: T;
private theStages: Stage;
}
`,
},
],
invalid: [
{
errors: [
{
messageId: 'summary',
},
],
filename: 'src/commands/foo.ts',
code: `
export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
public static readonly description = 'bar'
public static readonly examples = 'baz'
}
`,
},
],
});

0 comments on commit 8461761

Please sign in to comment.