@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,162 @@
1
+ 'use strict';
2
+
3
+ const { findVariable } = require('eslint-utils');
4
+ const utils = require('../utils');
5
+
6
+ // ------------------------------------------------------------------------------
7
+ // Rule Definition
8
+ // ------------------------------------------------------------------------------
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ module.exports = {
12
+ meta: {
13
+ type: 'suggestion',
14
+ docs: {
15
+ description: 'require rules to implement a `meta.schema` property',
16
+ category: 'Rules',
17
+ recommended: true,
18
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-schema.md',
19
+ },
20
+ hasSuggestions: true,
21
+ schema: [
22
+ {
23
+ type: 'object',
24
+ properties: {
25
+ requireSchemaPropertyWhenOptionless: {
26
+ type: 'boolean',
27
+ default: true,
28
+ description:
29
+ 'Whether the rule should require the `meta.schema` property to be specified (with `schema: []`) for rules that have no options.',
30
+ },
31
+ },
32
+ additionalProperties: false,
33
+ },
34
+ ],
35
+ messages: {
36
+ addEmptySchema: 'Add empty schema indicating the rule has no options.',
37
+ foundOptionsUsage:
38
+ '`meta.schema` has no schema defined but rule has options.',
39
+ missing: '`meta.schema` is required (use [] if rule has no schema).',
40
+ wrongType:
41
+ '`meta.schema` should be an array or object (use [] if rule has no schema).',
42
+ },
43
+ },
44
+
45
+ create(context) {
46
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
47
+ const { scopeManager } = sourceCode;
48
+ const ruleInfo = utils.getRuleInfo(sourceCode);
49
+ if (!ruleInfo) {
50
+ return {};
51
+ }
52
+
53
+ let contextIdentifiers;
54
+ const metaNode = ruleInfo.meta;
55
+ let schemaNode;
56
+
57
+ // Options
58
+ const requireSchemaPropertyWhenOptionless =
59
+ !context.options[0] ||
60
+ context.options[0].requireSchemaPropertyWhenOptionless;
61
+
62
+ let hasEmptySchema = false;
63
+ let isUsingOptions = false;
64
+
65
+ return {
66
+ Program(ast) {
67
+ contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
68
+
69
+ schemaNode = utils
70
+ .evaluateObjectProperties(metaNode, scopeManager)
71
+ .find(
72
+ (p) => p.type === 'Property' && utils.getKeyName(p) === 'schema'
73
+ );
74
+
75
+ if (!schemaNode) {
76
+ return;
77
+ }
78
+
79
+ let { value } = schemaNode;
80
+ if (value.type === 'Identifier' && value.name !== 'undefined') {
81
+ const variable = findVariable(
82
+ scopeManager.acquire(value) || scopeManager.globalScope,
83
+ value
84
+ );
85
+
86
+ // If we can't find the declarator, we have to assume it's in correct type
87
+ if (
88
+ !variable ||
89
+ !variable.defs ||
90
+ !variable.defs[0] ||
91
+ !variable.defs[0].node ||
92
+ variable.defs[0].node.type !== 'VariableDeclarator' ||
93
+ !variable.defs[0].node.init
94
+ ) {
95
+ return;
96
+ }
97
+
98
+ value = variable.defs[0].node.init;
99
+ }
100
+
101
+ if (
102
+ (value.type === 'ArrayExpression' && value.elements.length === 0) ||
103
+ (value.type === 'ObjectExpression' && value.properties.length === 0)
104
+ ) {
105
+ // Schema is explicitly defined as having no options.
106
+ hasEmptySchema = true;
107
+ }
108
+
109
+ if (
110
+ value.type === 'Literal' ||
111
+ (value.type === 'Identifier' && value.name === 'undefined')
112
+ ) {
113
+ context.report({ node: value, messageId: 'wrongType' });
114
+ }
115
+ },
116
+
117
+ 'Program:exit'() {
118
+ if (!schemaNode && requireSchemaPropertyWhenOptionless) {
119
+ context.report({
120
+ node: metaNode || ruleInfo.create,
121
+ messageId: 'missing',
122
+ suggest:
123
+ !isUsingOptions &&
124
+ metaNode &&
125
+ metaNode.type === 'ObjectExpression'
126
+ ? [
127
+ {
128
+ messageId: 'addEmptySchema',
129
+ fix(fixer) {
130
+ return utils.insertProperty(
131
+ fixer,
132
+ metaNode,
133
+ 'schema: []',
134
+ sourceCode
135
+ );
136
+ },
137
+ },
138
+ ]
139
+ : [],
140
+ });
141
+ }
142
+ },
143
+
144
+ MemberExpression(node) {
145
+ // Check if `context.options` was used when no options were defined in `meta.schema`.
146
+ if (
147
+ (hasEmptySchema || !schemaNode) &&
148
+ node.object.type === 'Identifier' &&
149
+ contextIdentifiers.has(node.object) &&
150
+ node.property.type === 'Identifier' &&
151
+ node.property.name === 'options'
152
+ ) {
153
+ isUsingOptions = true;
154
+ context.report({
155
+ node: schemaNode || metaNode || ruleInfo.create,
156
+ messageId: 'foundOptionsUsage',
157
+ });
158
+ }
159
+ },
160
+ };
161
+ },
162
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview require rules to implement a `meta.type` property
3
+ * @author 薛定谔的猫<weiran.zsd@outlook.com>
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { getStaticValue } = require('eslint-utils');
9
+ const utils = require('../utils');
10
+ const VALID_TYPES = new Set(['problem', 'suggestion', 'layout']);
11
+
12
+ // ------------------------------------------------------------------------------
13
+ // Rule Definition
14
+ // ------------------------------------------------------------------------------
15
+
16
+ /** @type {import('eslint').Rule.RuleModule} */
17
+ module.exports = {
18
+ meta: {
19
+ type: 'problem',
20
+ docs: {
21
+ description: 'require rules to implement a `meta.type` property',
22
+ category: 'Rules',
23
+ recommended: true,
24
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-type.md',
25
+ },
26
+ fixable: null,
27
+ schema: [],
28
+ messages: {
29
+ missing:
30
+ '`meta.type` is required (must be either `problem`, `suggestion`, or `layout`).',
31
+ unexpected:
32
+ '`meta.type` must be either `problem`, `suggestion`, or `layout`.',
33
+ },
34
+ },
35
+
36
+ create(context) {
37
+ // ----------------------------------------------------------------------
38
+ // Public
39
+ // ----------------------------------------------------------------------
40
+
41
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
42
+ const ruleInfo = utils.getRuleInfo(sourceCode);
43
+ if (!ruleInfo) {
44
+ return {};
45
+ }
46
+
47
+ return {
48
+ Program(node) {
49
+ const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0
50
+ const { scopeManager } = sourceCode;
51
+
52
+ const metaNode = ruleInfo.meta;
53
+ const typeNode = utils
54
+ .evaluateObjectProperties(metaNode, scopeManager)
55
+ .find((p) => p.type === 'Property' && utils.getKeyName(p) === 'type');
56
+
57
+ if (!typeNode) {
58
+ context.report({
59
+ node: metaNode || ruleInfo.create,
60
+ messageId: 'missing',
61
+ });
62
+ return;
63
+ }
64
+
65
+ const staticValue = getStaticValue(typeNode.value, scope);
66
+ if (!staticValue) {
67
+ // Ignore non-static values since we can't determine what they look like.
68
+ return;
69
+ }
70
+
71
+ if (!VALID_TYPES.has(staticValue.value)) {
72
+ context.report({ node: typeNode.value, messageId: 'unexpected' });
73
+ }
74
+ },
75
+ };
76
+ },
77
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @fileoverview Requires the properties of a test case to be placed in a consistent order.
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 the properties of a test case to be placed in a consistent order',
21
+ category: 'Tests',
22
+ recommended: false,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/test-case-property-ordering.md',
24
+ },
25
+ fixable: 'code',
26
+ schema: [
27
+ {
28
+ type: 'array',
29
+ elements: { type: 'string' },
30
+ },
31
+ ],
32
+ messages: {
33
+ inconsistentOrder:
34
+ 'The properties of a test case should be placed in a consistent order: [{{order}}].',
35
+ },
36
+ },
37
+
38
+ create(context) {
39
+ // ----------------------------------------------------------------------
40
+ // Public
41
+ // ----------------------------------------------------------------------
42
+ const order = context.options[0] || [
43
+ 'filename',
44
+ 'code',
45
+ 'output',
46
+ 'options',
47
+ 'parser',
48
+ 'languageOptions', // flat-mode only
49
+ 'parserOptions', // eslintrc-mode only
50
+ 'globals', // eslintrc-mode only
51
+ 'env', // eslintrc-mode only
52
+ 'errors',
53
+ ];
54
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
55
+
56
+ return {
57
+ Program(ast) {
58
+ utils.getTestInfo(context, ast).forEach((testRun) => {
59
+ [testRun.valid, testRun.invalid].forEach((tests) => {
60
+ tests.forEach((test) => {
61
+ const properties = (test && test.properties) || [];
62
+ const keyNames = properties.map(utils.getKeyName);
63
+
64
+ for (let i = 0, lastChecked; i < keyNames.length; i++) {
65
+ const current = order.indexOf(keyNames[i]);
66
+
67
+ // current < lastChecked to catch unordered;
68
+ // and lastChecked === -1 to catch extra properties before.
69
+ if (
70
+ current > -1 &&
71
+ (current < lastChecked || lastChecked === -1)
72
+ ) {
73
+ let orderMsg = order.filter((item) =>
74
+ keyNames.includes(item)
75
+ );
76
+ orderMsg = [
77
+ ...orderMsg,
78
+ ...(lastChecked === -1
79
+ ? keyNames.filter((item) => !order.includes(item))
80
+ : []),
81
+ ];
82
+
83
+ context.report({
84
+ node: properties[i],
85
+ messageId: 'inconsistentOrder',
86
+ data: { order: orderMsg.join(', ') },
87
+ fix(fixer) {
88
+ return orderMsg.map((key, index) => {
89
+ const propertyToInsert =
90
+ properties[keyNames.indexOf(key)];
91
+ return fixer.replaceText(
92
+ properties[index],
93
+ sourceCode.getText(propertyToInsert)
94
+ );
95
+ });
96
+ },
97
+ });
98
+ }
99
+ lastChecked = current;
100
+ }
101
+ });
102
+ });
103
+ });
104
+ },
105
+ };
106
+ },
107
+ };
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @fileoverview Enforce consistent usage of shorthand strings for test cases with no options
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
+ 'enforce consistent usage of shorthand strings for test cases with no options',
21
+ category: 'Tests',
22
+ recommended: false,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/test-case-shorthand-strings.md',
24
+ },
25
+ fixable: 'code',
26
+ schema: [
27
+ { enum: ['as-needed', 'never', 'consistent', 'consistent-as-needed'] },
28
+ ],
29
+ messages: {
30
+ useShorthand:
31
+ 'Use {{preferred}} for this test case instead of {{actual}}.',
32
+ },
33
+ },
34
+
35
+ create(context) {
36
+ const shorthandOption = context.options[0] || 'as-needed';
37
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
38
+
39
+ // ----------------------------------------------------------------------
40
+ // Helpers
41
+ // ----------------------------------------------------------------------
42
+
43
+ /**
44
+ * Reports test cases as necessary
45
+ * @param {object[]} cases A list of test case nodes
46
+ * @returns {void}
47
+ */
48
+ function reportTestCases(cases) {
49
+ const caseInfoList = cases
50
+ .map((testCase) => {
51
+ if (
52
+ testCase.type === 'Literal' ||
53
+ testCase.type === 'TemplateLiteral'
54
+ ) {
55
+ return { node: testCase, shorthand: true, needsLongform: false };
56
+ }
57
+ if (testCase.type === 'ObjectExpression') {
58
+ return {
59
+ node: testCase,
60
+ shorthand: false,
61
+ needsLongform: !(
62
+ testCase.properties.length === 1 &&
63
+ utils.getKeyName(testCase.properties[0]) === 'code'
64
+ ),
65
+ };
66
+ }
67
+ return null;
68
+ })
69
+ .filter(Boolean);
70
+
71
+ const isConsistent =
72
+ new Set(caseInfoList.map((caseInfo) => caseInfo.shorthand)).size <= 1;
73
+ const hasCaseNeedingLongform = caseInfoList.some(
74
+ (caseInfo) => caseInfo.needsLongform
75
+ );
76
+
77
+ caseInfoList
78
+ .filter(
79
+ {
80
+ 'as-needed': (caseInfo) =>
81
+ !caseInfo.shorthand && !caseInfo.needsLongform,
82
+ never: (caseInfo) => caseInfo.shorthand,
83
+ consistent: isConsistent
84
+ ? () => false
85
+ : (caseInfo) => caseInfo.shorthand,
86
+ 'consistent-as-needed': (caseInfo) =>
87
+ caseInfo.shorthand === hasCaseNeedingLongform,
88
+ }[shorthandOption]
89
+ )
90
+ .forEach((badCaseInfo) => {
91
+ context.report({
92
+ node: badCaseInfo.node,
93
+ messageId: 'useShorthand',
94
+ data: {
95
+ preferred: badCaseInfo.shorthand ? 'an object' : 'a string',
96
+ actual: badCaseInfo.shorthand ? 'a string' : 'an object',
97
+ },
98
+ fix(fixer) {
99
+ return fixer.replaceText(
100
+ badCaseInfo.node,
101
+ badCaseInfo.shorthand
102
+ ? `{code: ${sourceCode.getText(badCaseInfo.node)}}`
103
+ : sourceCode.getText(badCaseInfo.node.properties[0].value)
104
+ );
105
+ },
106
+ });
107
+ });
108
+ }
109
+
110
+ // ----------------------------------------------------------------------
111
+ // Public
112
+ // ----------------------------------------------------------------------
113
+
114
+ return {
115
+ Program(ast) {
116
+ utils
117
+ .getTestInfo(context, ast)
118
+ .map((testRun) => testRun.valid)
119
+ .filter(Boolean)
120
+ .forEach(reportTestCases);
121
+ },
122
+ };
123
+ },
124
+ };