nx/tools/eslint-rules/rules/valid-command-object.ts

88 lines
3.1 KiB
TypeScript

/**
* This file sets you up with structure needed for an ESLint rule.
*
* It leverages utilities from @typescript-eslint to allow TypeScript to
* provide autocompletions etc for the configuration.
*
* Your rule's custom logic will live within the create() method below
* and you can learn more about writing ESLint rules on the official guide:
*
* https://eslint.org/docs/developer-guide/working-with-rules
*
* You can also view many examples of existing rules here:
*
* https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin/src/rules
*/
import { ASTUtils, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-valid-command-object"
export const RULE_NAME = 'valid-command-object';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
fixable: 'code',
docs: {
description: `Ensures that commands contain valid descriptions in order to provide consistent --help output and generated documentation`,
},
schema: [],
messages: {
validCommandDescription:
'A command description should end with a . character for consistency',
},
},
defaultOptions: [],
create(context) {
if (
!context.physicalFilename.endsWith('command-object.ts') &&
!(
context.physicalFilename.includes('nx/src/command-line/') &&
// Ignore the examples file, those descriptions work differently
!context.physicalFilename.endsWith('examples.ts') &&
// Ignore spec files
!context.physicalFilename.endsWith('.spec.ts')
) &&
!context.physicalFilename.includes('packages/create-nx-workspace')
) {
return {};
}
return {
'Property > :matches(Identifier[name="describe"], Identifier[name="description"], TaggedTemplateExpression)':
(node: TSESTree.Identifier) => {
const propertyNode = node.parent as TSESTree.Property;
const stringToCheck =
(propertyNode.value.type === 'Literal' &&
typeof propertyNode.value.value !== 'boolean') ||
propertyNode.value.type === 'TemplateLiteral'
? ASTUtils.getStringIfConstant(propertyNode.value)
: null;
// String description already ends with a . character (or some other form of punctuation)
if (
!stringToCheck ||
// Call trim() to avoid issues with trailing whitespace
stringToCheck.trim().endsWith('.') ||
stringToCheck.trim().endsWith('!') ||
stringToCheck.trim().endsWith('?')
) {
return;
}
context.report({
messageId: 'validCommandDescription',
node: propertyNode,
fix: (fixer) => {
// We need to take the closing ' or " or ` into account when applying the . character
return fixer.insertTextAfterRange(
[propertyNode.value.range[0], propertyNode.value.range[1] - 1],
'.'
);
},
});
},
};
},
});