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