@sgfe/eslint-plugin-sg 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. package/LICENSE.md +25 -0
  2. package/README.md +188 -0
  3. package/configs/all-type-checked.js +10 -0
  4. package/configs/all.js +11 -0
  5. package/configs/recommended.js +11 -0
  6. package/configs/rules-recommended.js +11 -0
  7. package/configs/rules.js +11 -0
  8. package/configs/tests-recommended.js +11 -0
  9. package/configs/tests.js +11 -0
  10. package/lib/index.js +90 -0
  11. package/lib/rules/consistent-output.js +70 -0
  12. package/lib/rules/fixer-return.js +170 -0
  13. package/lib/rules/meta-property-ordering.js +108 -0
  14. package/lib/rules/no-deprecated-context-methods.js +98 -0
  15. package/lib/rules/no-deprecated-report-api.js +83 -0
  16. package/lib/rules/no-identical-tests.js +87 -0
  17. package/lib/rules/no-missing-message-ids.js +101 -0
  18. package/lib/rules/no-missing-placeholders.js +131 -0
  19. package/lib/rules/no-only-tests.js +99 -0
  20. package/lib/rules/no-property-in-node.js +86 -0
  21. package/lib/rules/no-unused-message-ids.js +139 -0
  22. package/lib/rules/no-unused-placeholders.js +127 -0
  23. package/lib/rules/no-useless-token-range.js +174 -0
  24. package/lib/rules/prefer-message-ids.js +109 -0
  25. package/lib/rules/prefer-object-rule.js +83 -0
  26. package/lib/rules/prefer-output-null.js +77 -0
  27. package/lib/rules/prefer-placeholders.js +102 -0
  28. package/lib/rules/prefer-replace-text.js +91 -0
  29. package/lib/rules/report-message-format.js +133 -0
  30. package/lib/rules/require-meta-docs-description.js +110 -0
  31. package/lib/rules/require-meta-docs-url.js +175 -0
  32. package/lib/rules/require-meta-fixable.js +137 -0
  33. package/lib/rules/require-meta-has-suggestions.js +168 -0
  34. package/lib/rules/require-meta-schema.js +162 -0
  35. package/lib/rules/require-meta-type.js +77 -0
  36. package/lib/rules/test-case-property-ordering.js +107 -0
  37. package/lib/rules/test-case-shorthand-strings.js +124 -0
  38. package/lib/utils.js +936 -0
  39. package/package.json +76 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @fileoverview Enforces the order of meta properties
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const { getKeyName, getRuleInfo } = require('../utils');
8
+
9
+ // ------------------------------------------------------------------------------
10
+ // Rule Definition
11
+ // ------------------------------------------------------------------------------
12
+
13
+ /** @type {import('eslint').Rule.RuleModule} */
14
+ module.exports = {
15
+ meta: {
16
+ type: 'suggestion',
17
+ docs: {
18
+ description: 'enforce the order of meta properties',
19
+ category: 'Rules',
20
+ recommended: false,
21
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/meta-property-ordering.md',
22
+ },
23
+ fixable: 'code',
24
+ schema: [
25
+ {
26
+ type: 'array',
27
+ elements: { type: 'string' },
28
+ },
29
+ ],
30
+ messages: {
31
+ inconsistentOrder:
32
+ 'The meta properties should be placed in a consistent order: [{{order}}].',
33
+ },
34
+ },
35
+
36
+ create(context) {
37
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
38
+ const ruleInfo = getRuleInfo(sourceCode);
39
+ if (!ruleInfo) {
40
+ return {};
41
+ }
42
+
43
+ const order = context.options[0] || [
44
+ 'type',
45
+ 'docs',
46
+ 'fixable',
47
+ 'hasSuggestions',
48
+ 'deprecated',
49
+ 'replacedBy',
50
+ 'schema',
51
+ 'defaultOptions', // https://github.com/eslint/rfcs/tree/main/designs/2023-rule-options-defaults
52
+ 'messages',
53
+ ];
54
+
55
+ const orderMap = new Map(order.map((name, i) => [name, i]));
56
+
57
+ return {
58
+ Program() {
59
+ if (!ruleInfo.meta || ruleInfo.meta.properties.length < 2) {
60
+ return;
61
+ }
62
+
63
+ const props = ruleInfo.meta.properties;
64
+
65
+ let last;
66
+
67
+ const violatingProps = props.filter((prop) => {
68
+ const curr = orderMap.has(getKeyName(prop))
69
+ ? orderMap.get(getKeyName(prop))
70
+ : Number.POSITIVE_INFINITY;
71
+ return last > (last = curr);
72
+ });
73
+
74
+ if (violatingProps.length === 0) {
75
+ return;
76
+ }
77
+
78
+ const knownProps = props
79
+ .filter((prop) => orderMap.has(getKeyName(prop)))
80
+ .sort(
81
+ (a, b) => orderMap.get(getKeyName(a)) - orderMap.get(getKeyName(b))
82
+ );
83
+ const unknownProps = props.filter(
84
+ (prop) => !orderMap.has(getKeyName(prop))
85
+ );
86
+
87
+ for (const violatingProp of violatingProps) {
88
+ context.report({
89
+ node: violatingProp,
90
+ messageId: 'inconsistentOrder',
91
+ data: {
92
+ order: knownProps.map(getKeyName).join(', '),
93
+ },
94
+ fix(fixer) {
95
+ const expectedProps = [...knownProps, ...unknownProps];
96
+ return props.map((prop, k) => {
97
+ return fixer.replaceText(
98
+ prop,
99
+ sourceCode.getText(expectedProps[k])
100
+ );
101
+ });
102
+ },
103
+ });
104
+ }
105
+ },
106
+ };
107
+ },
108
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @fileoverview Disallows usage of deprecated methods on rule context objects
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+
10
+ const DEPRECATED_PASSTHROUGHS = {
11
+ getSource: 'getText',
12
+ getSourceLines: 'getLines',
13
+ getAllComments: 'getAllComments',
14
+ getNodeByRangeIndex: 'getNodeByRangeIndex',
15
+ getComments: 'getComments',
16
+ getCommentsBefore: 'getCommentsBefore',
17
+ getCommentsAfter: 'getCommentsAfter',
18
+ getCommentsInside: 'getCommentsInside',
19
+ getJSDocComment: 'getJSDocComment',
20
+ getFirstToken: 'getFirstToken',
21
+ getFirstTokens: 'getFirstTokens',
22
+ getLastToken: 'getLastToken',
23
+ getLastTokens: 'getLastTokens',
24
+ getTokenAfter: 'getTokenAfter',
25
+ getTokenBefore: 'getTokenBefore',
26
+ getTokenByRangeStart: 'getTokenByRangeStart',
27
+ getTokens: 'getTokens',
28
+ getTokensAfter: 'getTokensAfter',
29
+ getTokensBefore: 'getTokensBefore',
30
+ getTokensBetween: 'getTokensBetween',
31
+ };
32
+
33
+ // ------------------------------------------------------------------------------
34
+ // Rule Definition
35
+ // ------------------------------------------------------------------------------
36
+
37
+ /** @type {import('eslint').Rule.RuleModule} */
38
+ module.exports = {
39
+ meta: {
40
+ type: 'suggestion',
41
+ docs: {
42
+ description:
43
+ 'disallow usage of deprecated methods on rule context objects',
44
+ category: 'Rules',
45
+ recommended: true,
46
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-deprecated-context-methods.md',
47
+ },
48
+ fixable: 'code',
49
+ schema: [],
50
+ messages: {
51
+ newFormat:
52
+ 'Use `{{contextName}}.getSourceCode().{{replacement}}` instead of `{{contextName}}.{{original}}`.',
53
+ },
54
+ },
55
+
56
+ create(context) {
57
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
58
+
59
+ // ----------------------------------------------------------------------
60
+ // Public
61
+ // ----------------------------------------------------------------------
62
+
63
+ return {
64
+ 'Program:exit'(ast) {
65
+ [...utils.getContextIdentifiers(sourceCode.scopeManager, ast)]
66
+ .filter(
67
+ (contextId) =>
68
+ contextId.parent.type === 'MemberExpression' &&
69
+ contextId === contextId.parent.object &&
70
+ contextId.parent.property.type === 'Identifier' &&
71
+ Object.prototype.hasOwnProperty.call(
72
+ DEPRECATED_PASSTHROUGHS,
73
+ contextId.parent.property.name
74
+ )
75
+ )
76
+ .forEach((contextId) =>
77
+ context.report({
78
+ node: contextId.parent,
79
+ messageId: 'newFormat',
80
+ data: {
81
+ contextName: contextId.name,
82
+ original: contextId.parent.property.name,
83
+ replacement:
84
+ DEPRECATED_PASSTHROUGHS[contextId.parent.property.name],
85
+ },
86
+ fix: (fixer) => [
87
+ fixer.insertTextAfter(contextId, '.getSourceCode()'),
88
+ fixer.replaceText(
89
+ contextId.parent.property,
90
+ DEPRECATED_PASSTHROUGHS[contextId.parent.property.name]
91
+ ),
92
+ ],
93
+ })
94
+ );
95
+ },
96
+ };
97
+ },
98
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @fileoverview Disallow the version of `context.report()` with multiple arguments
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+
10
+ // ------------------------------------------------------------------------------
11
+ // Rule Definition
12
+ // ------------------------------------------------------------------------------
13
+
14
+ /** @type {import('eslint').Rule.RuleModule} */
15
+ module.exports = {
16
+ meta: {
17
+ type: 'suggestion',
18
+ docs: {
19
+ description:
20
+ 'disallow the version of `context.report()` with multiple arguments',
21
+ category: 'Rules',
22
+ recommended: true,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-deprecated-report-api.md',
24
+ },
25
+ fixable: 'code', // or "code" or "whitespace"
26
+ schema: [],
27
+ messages: {
28
+ useNewAPI: 'Use the new-style context.report() API.',
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
34
+ let contextIdentifiers;
35
+
36
+ // ----------------------------------------------------------------------
37
+ // Public
38
+ // ----------------------------------------------------------------------
39
+
40
+ return {
41
+ Program(ast) {
42
+ contextIdentifiers = utils.getContextIdentifiers(
43
+ sourceCode.scopeManager,
44
+ ast
45
+ );
46
+ },
47
+ CallExpression(node) {
48
+ if (
49
+ node.callee.type === 'MemberExpression' &&
50
+ contextIdentifiers.has(node.callee.object) &&
51
+ node.callee.property.type === 'Identifier' &&
52
+ node.callee.property.name === 'report' &&
53
+ (node.arguments.length > 1 ||
54
+ (node.arguments.length === 1 &&
55
+ node.arguments[0].type === 'SpreadElement'))
56
+ ) {
57
+ context.report({
58
+ node: node.callee.property,
59
+ messageId: 'useNewAPI',
60
+ fix(fixer) {
61
+ const openingParen = sourceCode.getTokenBefore(node.arguments[0]);
62
+ const closingParen = sourceCode.getLastToken(node);
63
+ const reportInfo = utils.getReportInfo(node, context);
64
+
65
+ if (!reportInfo) {
66
+ return null;
67
+ }
68
+
69
+ return fixer.replaceTextRange(
70
+ [openingParen.range[1], closingParen.range[0]],
71
+ `{${Object.keys(reportInfo)
72
+ .map(
73
+ (key) => `${key}: ${sourceCode.getText(reportInfo[key])}`
74
+ )
75
+ .join(', ')}}`
76
+ );
77
+ },
78
+ });
79
+ }
80
+ },
81
+ };
82
+ },
83
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @fileoverview disallow identical tests
3
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+
10
+ // ------------------------------------------------------------------------------
11
+ // Rule Definition
12
+ // ------------------------------------------------------------------------------
13
+
14
+ /** @type {import('eslint').Rule.RuleModule} */
15
+ module.exports = {
16
+ meta: {
17
+ type: 'problem',
18
+ docs: {
19
+ description: 'disallow identical tests',
20
+ category: 'Tests',
21
+ recommended: true,
22
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-identical-tests.md',
23
+ },
24
+ fixable: 'code',
25
+ schema: [],
26
+ messages: {
27
+ identical: 'This test case is identical to another case.',
28
+ },
29
+ },
30
+
31
+ create(context) {
32
+ // ----------------------------------------------------------------------
33
+ // Public
34
+ // ----------------------------------------------------------------------
35
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
36
+
37
+ // ----------------------------------------------------------------------
38
+ // Helpers
39
+ // ----------------------------------------------------------------------
40
+ /**
41
+ * Create a unique cache key
42
+ * @param {object} test
43
+ * @returns {string}
44
+ */
45
+ function toKey(test) {
46
+ if (test.type !== 'ObjectExpression') {
47
+ return JSON.stringify([test.type, sourceCode.getText(test)]);
48
+ }
49
+ return JSON.stringify([
50
+ test.type,
51
+ ...test.properties.map((p) => sourceCode.getText(p)).sort(),
52
+ ]);
53
+ }
54
+
55
+ return {
56
+ Program(ast) {
57
+ utils.getTestInfo(context, ast).forEach((testRun) => {
58
+ [testRun.valid, testRun.invalid].forEach((tests) => {
59
+ const cache = new Set();
60
+ tests.forEach((test) => {
61
+ const key = toKey(test);
62
+ if (cache.has(key)) {
63
+ context.report({
64
+ node: test,
65
+ messageId: 'identical',
66
+ fix(fixer) {
67
+ const start = sourceCode.getTokenBefore(test);
68
+ const end = sourceCode.getTokenAfter(test);
69
+ return fixer.removeRange(
70
+ // should remove test's trailing comma
71
+ [
72
+ start.range[1],
73
+ end.value === ',' ? end.range[1] : test.range[1],
74
+ ]
75
+ );
76
+ },
77
+ });
78
+ } else {
79
+ cache.add(key);
80
+ }
81
+ });
82
+ });
83
+ });
84
+ },
85
+ };
86
+ },
87
+ };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../utils');
4
+
5
+ // ------------------------------------------------------------------------------
6
+ // Rule Definition
7
+ // ------------------------------------------------------------------------------
8
+
9
+ /** @type {import('eslint').Rule.RuleModule} */
10
+ module.exports = {
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description:
15
+ 'disallow `messageId`s that are missing from `meta.messages`',
16
+ category: 'Rules',
17
+ recommended: true,
18
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-missing-message-ids.md',
19
+ },
20
+ fixable: null,
21
+ schema: [],
22
+ messages: {
23
+ missingMessage:
24
+ '`meta.messages` is missing the messageId "{{messageId}}".',
25
+ },
26
+ },
27
+
28
+ create(context) {
29
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
30
+ const { scopeManager } = sourceCode;
31
+ const ruleInfo = utils.getRuleInfo(sourceCode);
32
+ if (!ruleInfo) {
33
+ return {};
34
+ }
35
+
36
+ const messagesNode = utils.getMessagesNode(ruleInfo, scopeManager);
37
+
38
+ let contextIdentifiers;
39
+
40
+ if (!messagesNode || messagesNode.type !== 'ObjectExpression') {
41
+ // If we can't find `meta.messages`, disable the rule.
42
+ return {};
43
+ }
44
+
45
+ return {
46
+ Program(ast) {
47
+ contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
48
+ },
49
+
50
+ CallExpression(node) {
51
+ const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0
52
+ // Check for messageId properties used in known calls to context.report();
53
+ if (
54
+ node.callee.type === 'MemberExpression' &&
55
+ contextIdentifiers.has(node.callee.object) &&
56
+ node.callee.property.type === 'Identifier' &&
57
+ node.callee.property.name === 'report'
58
+ ) {
59
+ const reportInfo = utils.getReportInfo(node, context);
60
+ if (!reportInfo) {
61
+ return;
62
+ }
63
+
64
+ const reportMessagesAndDataArray =
65
+ utils.collectReportViolationAndSuggestionData(reportInfo);
66
+ for (const { messageId } of reportMessagesAndDataArray.filter(
67
+ (obj) => obj.messageId
68
+ )) {
69
+ const values =
70
+ messageId.type === 'Literal'
71
+ ? [messageId]
72
+ : utils.findPossibleVariableValues(messageId, scopeManager);
73
+
74
+ // Look for any possible string values we found for this messageId.
75
+ values.forEach((val) => {
76
+ if (
77
+ val.type === 'Literal' &&
78
+ typeof val.value === 'string' &&
79
+ val.value !== '' &&
80
+ !utils.getMessageIdNodeById(
81
+ val.value,
82
+ ruleInfo,
83
+ scopeManager,
84
+ scope
85
+ )
86
+ )
87
+ // Couldn't find this messageId in `meta.messages`.
88
+ context.report({
89
+ node: val,
90
+ messageId: 'missingMessage',
91
+ data: {
92
+ messageId: val.value,
93
+ },
94
+ });
95
+ });
96
+ }
97
+ }
98
+ },
99
+ };
100
+ },
101
+ };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Disallow missing placeholders in rule report messages
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+ const { getStaticValue } = require('eslint-utils');
10
+
11
+ // ------------------------------------------------------------------------------
12
+ // Rule Definition
13
+ // ------------------------------------------------------------------------------
14
+
15
+ /** @type {import('eslint').Rule.RuleModule} */
16
+ module.exports = {
17
+ meta: {
18
+ type: 'problem',
19
+ docs: {
20
+ description: 'disallow missing placeholders in rule report messages',
21
+ category: 'Rules',
22
+ recommended: true,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-missing-placeholders.md',
24
+ },
25
+ fixable: null,
26
+ schema: [],
27
+ messages: {
28
+ placeholderDoesNotExist:
29
+ "The placeholder {{{{missingKey}}}} is missing (must provide it in the report's `data` object).",
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
35
+ const { scopeManager } = sourceCode;
36
+
37
+ let contextIdentifiers;
38
+
39
+ const ruleInfo = utils.getRuleInfo(sourceCode);
40
+ if (!ruleInfo) {
41
+ return {};
42
+ }
43
+
44
+ const messagesNode = utils.getMessagesNode(ruleInfo, scopeManager);
45
+
46
+ return {
47
+ Program(ast) {
48
+ contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
49
+ },
50
+ CallExpression(node) {
51
+ const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0
52
+ if (
53
+ node.callee.type === 'MemberExpression' &&
54
+ contextIdentifiers.has(node.callee.object) &&
55
+ node.callee.property.type === 'Identifier' &&
56
+ node.callee.property.name === 'report'
57
+ ) {
58
+ const reportInfo = utils.getReportInfo(node, context);
59
+ if (!reportInfo) {
60
+ return;
61
+ }
62
+
63
+ const reportMessagesAndDataArray =
64
+ utils.collectReportViolationAndSuggestionData(reportInfo);
65
+
66
+ if (messagesNode) {
67
+ // Check for any potential instances where we can use the messageId to fill in the message for convenience.
68
+ reportMessagesAndDataArray.forEach((obj) => {
69
+ if (
70
+ !obj.message &&
71
+ obj.messageId &&
72
+ obj.messageId.type === 'Literal' &&
73
+ typeof obj.messageId.value === 'string'
74
+ ) {
75
+ const correspondingMessage = utils.getMessageIdNodeById(
76
+ obj.messageId.value,
77
+ ruleInfo,
78
+ scopeManager,
79
+ scope
80
+ );
81
+ if (correspondingMessage) {
82
+ obj.message = correspondingMessage.value;
83
+ }
84
+ }
85
+ });
86
+ }
87
+
88
+ for (const {
89
+ message,
90
+ messageId,
91
+ data,
92
+ } of reportMessagesAndDataArray.filter((obj) => obj.message)) {
93
+ const messageStaticValue = getStaticValue(message, scope);
94
+ if (
95
+ ((message.type === 'Literal' &&
96
+ typeof message.value === 'string') ||
97
+ (messageStaticValue &&
98
+ typeof messageStaticValue.value === 'string')) &&
99
+ (!data || data.type === 'ObjectExpression')
100
+ ) {
101
+ // Same regex as the one ESLint uses
102
+ // https://github.com/eslint/eslint/blob/e5446449d93668ccbdb79d78cc69f165ce4fde07/lib/eslint.js#L990
103
+ const PLACEHOLDER_MATCHER = /{{\s*([^{}]+?)\s*}}/g;
104
+ let match;
105
+
106
+ while (
107
+ (match = PLACEHOLDER_MATCHER.exec(
108
+ message.value || messageStaticValue.value
109
+ ))
110
+ ) {
111
+ const matchingProperty =
112
+ data &&
113
+ data.properties.find(
114
+ (prop) => utils.getKeyName(prop) === match[1]
115
+ );
116
+
117
+ if (!matchingProperty) {
118
+ context.report({
119
+ node: data || messageId || message,
120
+ messageId: 'placeholderDoesNotExist',
121
+ data: { missingKey: match[1] },
122
+ });
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ },
129
+ };
130
+ },
131
+ };