@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,109 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../utils');
4
+ const { getStaticValue } = require('eslint-utils');
5
+
6
+ // ------------------------------------------------------------------------------
7
+ // Rule Definition
8
+ // ------------------------------------------------------------------------------
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ module.exports = {
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description:
16
+ 'require using `messageId` instead of `message` or `desc` to report rule violations',
17
+ category: 'Rules',
18
+ recommended: true,
19
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-message-ids.md',
20
+ },
21
+ fixable: null,
22
+ schema: [],
23
+ messages: {
24
+ messagesMissing:
25
+ '`meta.messages` must contain at least one violation message.',
26
+ foundMessage:
27
+ 'Use `messageId` instead of `message` (for violations) or `desc` (for suggestions).',
28
+ },
29
+ },
30
+
31
+ create(context) {
32
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
33
+ const ruleInfo = utils.getRuleInfo(sourceCode);
34
+ if (!ruleInfo) {
35
+ return {};
36
+ }
37
+
38
+ let contextIdentifiers;
39
+
40
+ // ----------------------------------------------------------------------
41
+ // Public
42
+ // ----------------------------------------------------------------------
43
+
44
+ return {
45
+ Program(ast) {
46
+ const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0
47
+ contextIdentifiers = utils.getContextIdentifiers(
48
+ sourceCode.scopeManager,
49
+ ast
50
+ );
51
+
52
+ const metaNode = ruleInfo.meta;
53
+ const messagesNode =
54
+ metaNode &&
55
+ metaNode.properties &&
56
+ metaNode.properties.find(
57
+ (p) => p.type === 'Property' && utils.getKeyName(p) === 'messages'
58
+ );
59
+
60
+ if (!messagesNode) {
61
+ context.report({
62
+ node: metaNode || ruleInfo.create,
63
+ messageId: 'messagesMissing',
64
+ });
65
+ return;
66
+ }
67
+
68
+ const staticValue = getStaticValue(messagesNode.value, scope);
69
+ if (!staticValue) {
70
+ return;
71
+ }
72
+
73
+ if (
74
+ typeof staticValue.value === 'object' &&
75
+ staticValue.value.constructor === Object &&
76
+ Object.keys(staticValue.value).length === 0
77
+ ) {
78
+ context.report({
79
+ node: messagesNode.value,
80
+ messageId: 'messagesMissing',
81
+ });
82
+ }
83
+ },
84
+ CallExpression(node) {
85
+ if (
86
+ node.callee.type === 'MemberExpression' &&
87
+ contextIdentifiers.has(node.callee.object) &&
88
+ node.callee.property.type === 'Identifier' &&
89
+ node.callee.property.name === 'report'
90
+ ) {
91
+ const reportInfo = utils.getReportInfo(node, context);
92
+ if (!reportInfo) {
93
+ return;
94
+ }
95
+
96
+ const reportMessagesAndDataArray = utils
97
+ .collectReportViolationAndSuggestionData(reportInfo)
98
+ .filter((obj) => obj.message);
99
+ for (const { message } of reportMessagesAndDataArray) {
100
+ context.report({
101
+ node: message.parent,
102
+ messageId: 'foundMessage',
103
+ });
104
+ }
105
+ }
106
+ },
107
+ };
108
+ },
109
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @author Brad Zacher <https://github.com/bradzacher>
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const utils = 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: 'disallow function-style rules',
19
+ category: 'Rules',
20
+ recommended: true,
21
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-object-rule.md',
22
+ },
23
+ fixable: 'code',
24
+ schema: [],
25
+ messages: {
26
+ preferObject: 'Rules should be declared using the object style.',
27
+ },
28
+ },
29
+
30
+ create(context) {
31
+ // ----------------------------------------------------------------------
32
+ // Public
33
+ // ----------------------------------------------------------------------
34
+
35
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
36
+ const ruleInfo = utils.getRuleInfo(sourceCode);
37
+ if (!ruleInfo) {
38
+ return {};
39
+ }
40
+
41
+ return {
42
+ Program() {
43
+ if (ruleInfo.isNewStyle) {
44
+ return;
45
+ }
46
+
47
+ context.report({
48
+ node: ruleInfo.create,
49
+ messageId: 'preferObject',
50
+ *fix(fixer) {
51
+ // note - we intentionally don't worry about formatting here, as otherwise we have
52
+ // to indent the function correctly
53
+
54
+ if (
55
+ ruleInfo.create.type === 'FunctionExpression' ||
56
+ ruleInfo.create.type === 'FunctionDeclaration'
57
+ ) {
58
+ const openParenToken = sourceCode.getFirstToken(
59
+ ruleInfo.create,
60
+ (token) => token.type === 'Punctuator' && token.value === '('
61
+ );
62
+
63
+ /* istanbul ignore if */
64
+ if (!openParenToken) {
65
+ // this shouldn't happen, but guarding against crashes just in case
66
+ return null;
67
+ }
68
+
69
+ yield fixer.replaceTextRange(
70
+ [ruleInfo.create.range[0], openParenToken.range[0]],
71
+ '{create'
72
+ );
73
+ yield fixer.insertTextAfter(ruleInfo.create, '}');
74
+ } else if (ruleInfo.create.type === 'ArrowFunctionExpression') {
75
+ yield fixer.insertTextBefore(ruleInfo.create, '{create: ');
76
+ yield fixer.insertTextAfter(ruleInfo.create, '}');
77
+ }
78
+ },
79
+ });
80
+ },
81
+ };
82
+ },
83
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview disallows invalid RuleTester test cases where the `output` matches the `code`
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: 'suggestion',
18
+ docs: {
19
+ description:
20
+ 'disallow invalid RuleTester test cases where the `output` matches the `code`',
21
+ category: 'Tests',
22
+ recommended: true,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-output-null.md',
24
+ },
25
+ fixable: 'code',
26
+ schema: [],
27
+ messages: {
28
+ useOutputNull:
29
+ 'Use `output: null` to assert that a test case is not autofixed.',
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ // ----------------------------------------------------------------------
35
+ // Public
36
+ // ----------------------------------------------------------------------
37
+
38
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
39
+
40
+ return {
41
+ Program(ast) {
42
+ utils.getTestInfo(context, ast).forEach((testRun) => {
43
+ testRun.invalid.forEach((test) => {
44
+ /**
45
+ * Get a test case's giving keyname node.
46
+ * @param {string} the keyname to find.
47
+ * @returns {Node} found node; if not found, return null;
48
+ */
49
+ function getTestInfo(key) {
50
+ if (test.type === 'ObjectExpression') {
51
+ return test.properties.find(
52
+ (item) => item.type === 'Property' && item.key.name === key
53
+ );
54
+ }
55
+ return null;
56
+ }
57
+
58
+ const code = getTestInfo('code');
59
+ const output = getTestInfo('output');
60
+
61
+ if (
62
+ output &&
63
+ sourceCode.getText(output.value) ===
64
+ sourceCode.getText(code.value)
65
+ ) {
66
+ context.report({
67
+ node: output,
68
+ messageId: 'useOutputNull',
69
+ fix: (fixer) => fixer.replaceText(output.value, 'null'),
70
+ });
71
+ }
72
+ });
73
+ });
74
+ },
75
+ };
76
+ },
77
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fileoverview require using placeholders for dynamic report messages
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+ const { findVariable } = require('eslint-utils');
10
+
11
+ // ------------------------------------------------------------------------------
12
+ // Rule Definition
13
+ // ------------------------------------------------------------------------------
14
+
15
+ /** @type {import('eslint').Rule.RuleModule} */
16
+ module.exports = {
17
+ meta: {
18
+ type: 'suggestion',
19
+ docs: {
20
+ description: 'require using placeholders for dynamic report messages',
21
+ category: 'Rules',
22
+ recommended: false,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-placeholders.md',
24
+ },
25
+ fixable: null,
26
+ schema: [],
27
+ messages: {
28
+ usePlaceholders:
29
+ 'Use report message placeholders instead of string concatenation.',
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ let contextIdentifiers;
35
+
36
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
37
+ const { scopeManager } = sourceCode;
38
+
39
+ // ----------------------------------------------------------------------
40
+ // Public
41
+ // ----------------------------------------------------------------------
42
+
43
+ return {
44
+ Program(ast) {
45
+ contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
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
+ ) {
54
+ const reportInfo = utils.getReportInfo(node, context);
55
+
56
+ if (!reportInfo) {
57
+ return;
58
+ }
59
+
60
+ const reportMessagesAndDataArray = utils
61
+ .collectReportViolationAndSuggestionData(reportInfo)
62
+ .filter((obj) => obj.message);
63
+ for (let { message: messageNode } of reportMessagesAndDataArray) {
64
+ if (messageNode.type === 'Identifier') {
65
+ // See if we can find the variable declaration.
66
+
67
+ const variable = findVariable(
68
+ scopeManager.acquire(messageNode) || scopeManager.globalScope,
69
+ messageNode
70
+ );
71
+
72
+ if (
73
+ !variable ||
74
+ !variable.defs ||
75
+ !variable.defs[0] ||
76
+ !variable.defs[0].node ||
77
+ variable.defs[0].node.type !== 'VariableDeclarator' ||
78
+ !variable.defs[0].node.init
79
+ ) {
80
+ return;
81
+ }
82
+
83
+ messageNode = variable.defs[0].node.init;
84
+ }
85
+
86
+ if (
87
+ (messageNode.type === 'TemplateLiteral' &&
88
+ messageNode.expressions.length > 0) ||
89
+ (messageNode.type === 'BinaryExpression' &&
90
+ messageNode.operator === '+')
91
+ ) {
92
+ context.report({
93
+ node: messageNode,
94
+ messageId: 'usePlaceholders',
95
+ });
96
+ }
97
+ }
98
+ }
99
+ },
100
+ };
101
+ },
102
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @fileoverview prefer using `replaceText()` instead of `replaceTextRange()`
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: 'suggestion',
18
+ docs: {
19
+ description:
20
+ 'require using `replaceText()` instead of `replaceTextRange()`',
21
+ category: 'Rules',
22
+ recommended: false,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/prefer-replace-text.md',
24
+ },
25
+ fixable: null,
26
+ schema: [],
27
+ messages: {
28
+ useReplaceText: 'Use replaceText instead of replaceTextRange.',
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
34
+ let funcInfo = {
35
+ upper: null,
36
+ codePath: null,
37
+ shouldCheck: false,
38
+ node: null,
39
+ };
40
+ let contextIdentifiers;
41
+
42
+ return {
43
+ Program(ast) {
44
+ contextIdentifiers = utils.getContextIdentifiers(
45
+ sourceCode.scopeManager,
46
+ ast
47
+ );
48
+ },
49
+
50
+ // Stacks this function's information.
51
+ onCodePathStart(codePath, node) {
52
+ funcInfo = {
53
+ upper: funcInfo,
54
+ codePath,
55
+ shouldCheck:
56
+ utils.isAutoFixerFunction(node, contextIdentifiers) ||
57
+ utils.isSuggestionFixerFunction(node, contextIdentifiers),
58
+ node,
59
+ };
60
+ },
61
+
62
+ // Pops this function's information.
63
+ onCodePathEnd() {
64
+ funcInfo = funcInfo.upper;
65
+ },
66
+
67
+ // Checks the replaceTextRange arguments.
68
+ 'CallExpression[arguments.length=2]'(node) {
69
+ if (
70
+ funcInfo.shouldCheck &&
71
+ node.callee.type === 'MemberExpression' &&
72
+ node.callee.property.name === 'replaceTextRange'
73
+ ) {
74
+ const arg = node.arguments[0];
75
+ const isIdenticalNodeRange =
76
+ arg.type === 'ArrayExpression' &&
77
+ arg.elements[0].type === 'MemberExpression' &&
78
+ arg.elements[1].type === 'MemberExpression' &&
79
+ sourceCode.getText(arg.elements[0].object) ===
80
+ sourceCode.getText(arg.elements[1].object);
81
+ if (isIdenticalNodeRange) {
82
+ context.report({
83
+ node,
84
+ messageId: 'useReplaceText',
85
+ });
86
+ }
87
+ }
88
+ },
89
+ };
90
+ },
91
+ };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @fileoverview enforce a consistent format for rule report messages
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { getStaticValue } = require('eslint-utils');
9
+ const utils = require('../utils');
10
+
11
+ // ------------------------------------------------------------------------------
12
+ // Rule Definition
13
+ // ------------------------------------------------------------------------------
14
+
15
+ /** @type {import('eslint').Rule.RuleModule} */
16
+ module.exports = {
17
+ meta: {
18
+ type: 'suggestion',
19
+ docs: {
20
+ description: 'enforce a consistent format for rule report messages',
21
+ category: 'Rules',
22
+ recommended: false,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/report-message-format.md',
24
+ },
25
+ fixable: null,
26
+ schema: [{ type: 'string' }],
27
+ messages: {
28
+ noMatch: "Report message does not match the pattern '{{pattern}}'.",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const pattern = new RegExp(context.options[0] || '');
34
+ let contextIdentifiers;
35
+
36
+ /**
37
+ * Report a message node if it doesn't match the given formatting
38
+ * @param {ASTNode} message The message AST node
39
+ * @returns {void}
40
+ */
41
+ function processMessageNode(message, scope) {
42
+ const staticValue = getStaticValue(message, scope);
43
+ if (
44
+ (message.type === 'Literal' &&
45
+ typeof message.value === 'string' &&
46
+ !pattern.test(message.value)) ||
47
+ (message.type === 'TemplateLiteral' &&
48
+ message.quasis.length === 1 &&
49
+ !pattern.test(message.quasis[0].value.cooked)) ||
50
+ (staticValue && !pattern.test(staticValue.value))
51
+ ) {
52
+ context.report({
53
+ node: message,
54
+ messageId: 'noMatch',
55
+ data: { pattern: context.options[0] || '' },
56
+ });
57
+ }
58
+ }
59
+
60
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
61
+ const ruleInfo = utils.getRuleInfo(sourceCode);
62
+ if (!ruleInfo) {
63
+ return {};
64
+ }
65
+
66
+ // ----------------------------------------------------------------------
67
+ // Public
68
+ // ----------------------------------------------------------------------
69
+
70
+ return {
71
+ Program(ast) {
72
+ const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0
73
+ contextIdentifiers = utils.getContextIdentifiers(
74
+ sourceCode.scopeManager,
75
+ ast
76
+ );
77
+
78
+ const messagesObject =
79
+ ruleInfo &&
80
+ ruleInfo.meta &&
81
+ ruleInfo.meta.type === 'ObjectExpression' &&
82
+ ruleInfo.meta.properties.find(
83
+ (prop) =>
84
+ prop.type === 'Property' && utils.getKeyName(prop) === 'messages'
85
+ );
86
+
87
+ if (
88
+ !messagesObject ||
89
+ messagesObject.value.type !== 'ObjectExpression'
90
+ ) {
91
+ return;
92
+ }
93
+
94
+ messagesObject.value.properties
95
+ .filter((prop) => prop.type === 'Property')
96
+ .map((prop) => prop.value)
97
+ .forEach((it) => processMessageNode(it, scope));
98
+ },
99
+ CallExpression(node) {
100
+ const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0
101
+ if (
102
+ node.callee.type === 'MemberExpression' &&
103
+ contextIdentifiers.has(node.callee.object) &&
104
+ node.callee.property.type === 'Identifier' &&
105
+ node.callee.property.name === 'report'
106
+ ) {
107
+ const reportInfo = utils.getReportInfo(node, context);
108
+ const message = reportInfo && reportInfo.message;
109
+ const suggest = reportInfo && reportInfo.suggest;
110
+
111
+ if (message) {
112
+ processMessageNode(message, scope);
113
+ }
114
+
115
+ if (suggest && suggest.type === 'ArrayExpression') {
116
+ suggest.elements
117
+ .flatMap((obj) =>
118
+ obj.type === 'ObjectExpression' ? obj.properties : []
119
+ )
120
+ .filter(
121
+ (prop) =>
122
+ prop.type === 'Property' &&
123
+ prop.key.type === 'Identifier' &&
124
+ prop.key.name === 'message'
125
+ )
126
+ .map((prop) => prop.value)
127
+ .forEach((it) => processMessageNode(it, scope));
128
+ }
129
+ }
130
+ },
131
+ };
132
+ },
133
+ };