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