@sgfe/eslint-plugin-sg 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE.md +25 -0
- package/README.md +188 -0
- package/configs/all-type-checked.js +10 -0
- package/configs/all.js +11 -0
- package/configs/recommended.js +11 -0
- package/configs/rules-recommended.js +11 -0
- package/configs/rules.js +11 -0
- package/configs/tests-recommended.js +11 -0
- package/configs/tests.js +11 -0
- package/lib/index.js +90 -0
- package/lib/rules/consistent-output.js +70 -0
- package/lib/rules/fixer-return.js +170 -0
- package/lib/rules/meta-property-ordering.js +108 -0
- package/lib/rules/no-deprecated-context-methods.js +98 -0
- package/lib/rules/no-deprecated-report-api.js +83 -0
- package/lib/rules/no-identical-tests.js +87 -0
- package/lib/rules/no-missing-message-ids.js +101 -0
- package/lib/rules/no-missing-placeholders.js +131 -0
- package/lib/rules/no-only-tests.js +99 -0
- package/lib/rules/no-property-in-node.js +86 -0
- package/lib/rules/no-unused-message-ids.js +139 -0
- package/lib/rules/no-unused-placeholders.js +127 -0
- package/lib/rules/no-useless-token-range.js +174 -0
- package/lib/rules/prefer-message-ids.js +109 -0
- package/lib/rules/prefer-object-rule.js +83 -0
- package/lib/rules/prefer-output-null.js +77 -0
- package/lib/rules/prefer-placeholders.js +102 -0
- package/lib/rules/prefer-replace-text.js +91 -0
- package/lib/rules/report-message-format.js +133 -0
- package/lib/rules/require-meta-docs-description.js +110 -0
- package/lib/rules/require-meta-docs-url.js +175 -0
- package/lib/rules/require-meta-fixable.js +137 -0
- package/lib/rules/require-meta-has-suggestions.js +168 -0
- package/lib/rules/require-meta-schema.js +162 -0
- package/lib/rules/require-meta-type.js +77 -0
- package/lib/rules/test-case-property-ordering.js +107 -0
- package/lib/rules/test-case-shorthand-strings.js +124 -0
- package/lib/utils.js +936 -0
- 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
|
+
};
|