Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sm/simplify-message-imports #358

Merged
merged 13 commits into from
Dec 14, 2023
Merged
5 changes: 4 additions & 1 deletion .github/workflows/externalLint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
command:
required: false
type: string
default: yarn eslint "src/**/*.ts"
default: yarn eslint "src/**/*.ts" --fix
description: 'command to execute (ex: yarn lint)'
preBuildCommands:
required: false
Expand All @@ -39,6 +39,9 @@ jobs:
with:
node-version: ${{ inputs.nodeVersion }}
- run: git clone ${{ inputs.externalProjectGitUrl}} $(pwd)
- run: npm install -g yarn-deduplicate
- uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main
- run: npx yarn-deduplicate
- uses: salesforcecli/github-workflows/.github/actions/yarnInstallWithRetries@main
- name: swap this dependency for the version on this branch
run: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ jobs:
- https://github.com/salesforcecli/plugin-sobject
- https://github.com/salesforcecli/plugin-templates
- https://github.com/salesforcecli/plugin-user

command:
- yarn eslint "src/**/*.ts" --fix
- yarn eslint "src/**/*.ts"
with:
packageName: 'eslint-plugin-sf-plugin'
externalProjectGitUrl: ${{ matrix.externalProjectGitUrl }}
preBuildCommands: yarn remove @types/jest
command: ${{ matrix.command }}
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ yarn build
yarn add --dev file:/absolute/path/to/eslint-plugin-sf-plugin
```

If you're using @salesforce/dev-scripts, add the plugin in your `.sfdevrc.json` file to keep dev-scripts from trying to enforce version minimums

```json
{
"devDepOverrides": ["eslint-plugin-sf-plugin"]
}
```

To get changes in your IDE, restart the eslint server.

include in the plugin's `eslint.rc`
Expand Down
88 changes: 45 additions & 43 deletions README.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions docs/rules/esm-message-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Looks for the verbose `Messages.importMessagesDirectory(dirname(fileURLToPath(import.meta.url)))` to offer a simpler alternative (`sf-plugin/esm-message-import`)

💼 This rule is enabled in the following configs: 📚 `library`, ✈️ `migration`, ✅ `recommended`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->
2 changes: 1 addition & 1 deletion docs/rules/no-execcmd-double-quotes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Do not use double quotes in NUT examples. They will not work on windows (`sf-plugin/no-execcmd-double-quotes`)

💼 This rule is enabled in the following configs: 📚 `library`, ✈️ `migration`, ✅ `recommended`.
🚫 This rule is _disabled_ in the following configs: 📚 `library`, ✈️ `migration`, ✅ `recommended`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

Expand Down
4 changes: 3 additions & 1 deletion docs/rules/no-hardcoded-messages-flags.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Use loaded messages and separate files for messages (`sf-plugin/no-hardcoded-messages-flags`)
# Use loaded messages and separate files for messages. Follow the message naming guidelines (`sf-plugin/no-hardcoded-messages-flags`)

⚠️ This rule _warns_ in the following configs: ✈️ `migration`, ✅ `recommended`.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->
25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-sf-plugin",
"version": "1.16.15",
"version": "1.16.16-qa.0",
"engines": {
"node": ">=16.0.0"
},
Expand All @@ -14,35 +14,36 @@
],
"repository": "salesforcecli/eslint-plugin-sf-plugin",
"dependencies": {
"@salesforce/core": "^5.3.20",
"@typescript-eslint/utils": "^5.59.11"
"@salesforce/core": "^6.4.0",
"@typescript-eslint/utils": "^6.13.2"
},
"devDependencies": {
"@salesforce/prettier-config": "^0.0.3",
"@types/eslint": "^8.44.8",
"@types/estree": "^1.0.5",
"@types/jest": "^28.1.7",
"@types/node": "^18.19.3",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.62.0",
"@types/jest": "^29.5.11",
"@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"@typescript-eslint/rule-tester": "^6.13.2",
"eslint": "^8.55.0",
"eslint-config-prettier": "^8.10.0",
"eslint-config-salesforce": "^2.0.2",
"eslint-config-salesforce-license": "^0.2.0",
"eslint-config-salesforce-typescript": "^1.1.3",
"eslint-doc-generator": "^1.5.2",
"eslint-doc-generator": "^1.6.1",
"eslint-plugin-eslint-plugin": "^5.1.1",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsdoc": "^46.9.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^7",
"jest": "^28.1.3",
"jest": "^29.7.0",
"lint-staged": "^13.3.0",
"prettier": "^2.8.8",
"pretty-quick": "^3.1.3",
"ts-jest": "^28.0.8",
"typescript": "^4.9.5"
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
},
"scripts": {
"build": "tsc -p .",
Expand Down Expand Up @@ -70,4 +71,4 @@
"path": "cz-conventional-changelog"
}
}
}
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ import { noHyphenAliases } from './rules/no-hyphens-aliases';
import { noClassesInCommandReturnType } from './rules/no-classes-in-command-return-type';
import { noExecCmdDoubleQuotes } from './rules/no-execCmd-double-quotes';
import { noMessagesLoad } from './rules/no-messages-load';
import { esmMessageImport } from './rules/esm-message-import';

const library = {
plugins: ['sf-plugin'],
rules: {
'sf-plugin/esm-message-import': 'error',
'sf-plugin/no-missing-messages': 'error',
'sf-plugin/no-messages-load': 'error',
'sf-plugin/no-execcmd-double-quotes': 'off',
Expand Down Expand Up @@ -110,6 +112,7 @@ export = {
},
},
rules: {
'esm-message-import': esmMessageImport,
'no-h-short-char': dashH,
'no-duplicate-short-characters': noDuplicateShortCharacters,
'run-matches-class-type': runMatchesClassType,
Expand Down
6 changes: 3 additions & 3 deletions src/rules/command-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
* 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';
import { RuleCreator } from '@typescript-eslint/utils/eslint-utils';

const createRule = ESLintUtils.RuleCreator((name) => `https://example.com/rule/${name}`);
const createRule = RuleCreator((name) => `https://example.com/rule/${name}`);

export const commandExamples = createRule({
name: 'command-example',
meta: {
docs: {
description: 'Ensure commands have a summary, description, and examples',
recommended: 'error',
recommended: 'stylistic',
},
messages: {
example: 'Commands should have an examples property',
Expand Down
8 changes: 5 additions & 3 deletions src/rules/command-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
* 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 { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { RuleCreator } from '@typescript-eslint/utils/eslint-utils';

import { extendsSfCommand, getClassPropertyIdentifierName, isInCommandDirectory } from '../shared/commands';
export const commandSummary = ESLintUtils.RuleCreator.withoutDocs({
export const commandSummary = RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Ensure commands have a summary',
recommended: 'error',
recommended: 'recommended',
},
messages: {
summary: 'Commands should have a summary property',
Expand Down
7 changes: 4 additions & 3 deletions src/rules/dash-h.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
* 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, AST_NODE_TYPES } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { RuleCreator } from '@typescript-eslint/utils/eslint-utils';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../shared/commands';
import { flagPropertyIsNamed, isFlag } from '../shared/flags';

export const dashH = ESLintUtils.RuleCreator.withoutDocs({
export const dashH = RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Do not allow creation of a flag with short char -h',
recommended: 'error',
recommended: 'recommended',
},
messages: {
message: '-h is reserved for help. Choose a different short character',
Expand Down
7 changes: 4 additions & 3 deletions src/rules/dash-o.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
* 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, AST_NODE_TYPES } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { RuleCreator } from '@typescript-eslint/utils/eslint-utils';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../shared/commands';
import { flagPropertyIsNamed, isFlag } from '../shared/flags';

export const dashO = ESLintUtils.RuleCreator.withoutDocs({
export const dashO = RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Warn on a flag that uses -o',
recommended: 'warn',
recommended: 'strict',
},
messages: {
message:
Expand Down
101 changes: 101 additions & 0 deletions src/rules/esm-message-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, the actual rule that led to this PR

* 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
*/
/* eslint-disable complexity */
import { RuleCreator } from '@typescript-eslint/utils/eslint-utils';
import { ASTUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';

export const esmMessageImport = RuleCreator.withoutDocs({
meta: {
docs: {
description:
'Looks for the verbose `Messages.importMessagesDirectory(dirname(fileURLToPath(import.meta.url)))` to offer a simpler alternative',
recommended: 'strict',
},
messages: {
changeImport: 'use Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) instead',
unnecessaryImport: 'the only code using this import was removed by this rule',
},
fixable: 'code',
type: 'problem',
schema: [],
},
defaultOptions: [],
create(context) {
return {
MemberExpression(node): void {
if (
node.object.type === AST_NODE_TYPES.Identifier &&
node.object.name === 'Messages' &&
node.property.type === AST_NODE_TYPES.Identifier &&
node.property.name === 'importMessagesDirectory' &&
node.parent?.parent &&
context.sourceCode.getText(node.parent.parent).includes('dirname(fileURLToPath(import.meta.url))')
) {
const toReplace = node.parent.parent;
// we never found the message at all, we can report and exit
return context.report({
node,
messageId: 'changeImport',
fix: (fixer) =>
fixer.replaceText(toReplace, 'Messages.importMessagesDirectoryFromMetaUrl(import.meta.url)'),
});
}
},
ImportDeclaration(node): void {
const allCode = context.sourceCode.getText();
// true for all cases
if (
((node.source.value === 'node:url' &&
node.specifiers.some((s) => s.local.name === 'fileURLToPath') &&
// it's the only reference to fileURLToPath
Array.from(allCode.matchAll(/fileURLToPath/g)).length === 1) ||
(node.source.value === 'node:path' &&
node.specifiers.some((s) => s.local.name === 'dirname') &&
// it's the only reference to dirname
Array.from(allCode.matchAll(/dirname/g)).length === 1)) &&
// we've already removed the old way of doing it
allCode.includes('Messages.importMessagesDirectoryFromMetaUrl(import.meta.url)')
) {
if (
// case 1: single specifier removes the entire import line
node.specifiers.length === 1 &&
node.specifiers[0].type === AST_NODE_TYPES.ImportSpecifier &&
node.specifiers[0].local.type === AST_NODE_TYPES.Identifier
) {
return context.report({
node,
messageId: 'unnecessaryImport',
fix: (fixer) => fixer.remove(node),
});
} else {
// case 2, just remove the 1 unused specifier
if (node.specifiers.length > 1) {
const replacementSpecifiers = node.specifiers.filter(
(s) => s.local.name !== 'dirname' && s.local.name !== 'fileURLToPath'
);

return context.report({
node,
messageId: 'unnecessaryImport',
fix: (fixer) =>
replacementSpecifiers.every(ASTUtils.isNodeOfType(AST_NODE_TYPES.ImportDefaultSpecifier))
? fixer.replaceText(
node,
`import ${replacementSpecifiers[0].local.name} from '${node.source.value}'`
)
: fixer.replaceTextRange(
[node.specifiers[0].range[0], node.specifiers[node.specifiers.length - 1].range[1]],
replacementSpecifiers.map((s) => context.sourceCode.getText(s)).join(', ')
),
});
}
}
}
},
};
},
});
7 changes: 4 additions & 3 deletions src/rules/extract-message-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
* 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 { RuleCreator } from '@typescript-eslint/utils/eslint-utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { extendsSfCommand, getClassPropertyIdentifierName, isInCommandDirectory } from '../shared/commands';

const propertiesYouShouldntHardCode = ['description', 'summary'];

export const extractMessageCommand = ESLintUtils.RuleCreator.withoutDocs({
export const extractMessageCommand = RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Use loaded messages and separate files for messages',
recommended: 'warn',
recommended: 'stylistic',
},
messages: {
message:
Expand Down
10 changes: 5 additions & 5 deletions src/rules/extract-message-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
* 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, AST_NODE_TYPES } from '@typescript-eslint/utils';
import { isNodeOfType } from '@typescript-eslint/utils/dist/ast-utils';
import { RuleCreator } from '@typescript-eslint/utils/eslint-utils';
import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
import { isFlag, resolveFlagName } from '../shared/flags';
import { ancestorsContainsSfCommand, isInCommandDirectory } from '../shared/commands';

export const extractMessageFlags = ESLintUtils.RuleCreator.withoutDocs({
export const extractMessageFlags = RuleCreator.withoutDocs({
meta: {
docs: {
description: 'Use loaded messages and separate files for messages. Follow the message naming guidelines',
recommended: 'warn',
recommended: 'stylistic',
},
fixable: 'code',
messages: {
Expand Down Expand Up @@ -45,7 +45,7 @@ export const extractMessageFlags = ESLintUtils.RuleCreator.withoutDocs({
messageId: 'message',
});
}
const flag = ancestors.filter(isNodeOfType(AST_NODE_TYPES.Property)).find((a) => isFlag(a));
const flag = ancestors.filter(ASTUtils.isNodeOfType(AST_NODE_TYPES.Property)).find((a) => isFlag(a));
const flagName = flag ? resolveFlagName(flag) : undefined;
if (
flagName &&
Expand Down
Loading