@sgfe/eslint-plugin-sg 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @sgfe/eslint-plugin-sg might be problematic. Click here for more details.

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
+ };