@zgeoff/oxlint-config 0.2.9 → 0.2.11
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.
- package/README.md +19 -1
- package/no-abbreviated-names.js +271 -0
- package/oxlintrc.json +30 -2
- package/package.json +2 -1
- package/plugin.js +2 -0
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ await arguments, awaits hidden inside a control-flow condition or a `&&`/`||`/`?
|
|
|
9
9
|
that loosens either comment rule. `zgeoff/function-verb` enforces the function-naming taxonomy from
|
|
10
10
|
the shared agent guidelines, `zgeoff/test-beside-module` keeps each test file beside the module it
|
|
11
11
|
tests, `zgeoff/import-zones` fails an import that crosses a module boundary the repo declares,
|
|
12
|
-
`zgeoff/no-restricted-words` keeps the words in its `words` option out of a path's text,
|
|
12
|
+
`zgeoff/no-restricted-words` keeps the words in its `words` option out of a path's text,
|
|
13
|
+
`zgeoff/no-abbreviated-names` reports each abbreviated value name it lists with its full word, and
|
|
13
14
|
`zgeoff/no-file-snapshot` bans file snapshots.
|
|
14
15
|
|
|
15
16
|
## Usage
|
|
@@ -106,6 +107,23 @@ the paths that may name the words:
|
|
|
106
107
|
}
|
|
107
108
|
```
|
|
108
109
|
|
|
110
|
+
## `zgeoff/no-abbreviated-names`
|
|
111
|
+
|
|
112
|
+
The `names` option maps each abbreviation to the text that replaces it, and the rule reports every
|
|
113
|
+
value name that has one of those names. A value name is any name that a declaration, an import, an
|
|
114
|
+
export alias, a key or a member write binds, so function, class and method names count beside
|
|
115
|
+
variables and properties. A key counts in each of its spellings, so `src`, `'src'`, `['src']` and
|
|
116
|
+
`#src` all report. A name matches whole, so `ms` reports `ms` but not `timeoutMs`.
|
|
117
|
+
|
|
118
|
+
The rule leaves alone the names that hold no value of their own. A destructured key that an alias
|
|
119
|
+
renames belongs to the object being read, so the rule reports only the alias. A computed key that
|
|
120
|
+
reads a variable, such as `[src]`, names no property, and the rule checks the variable at its
|
|
121
|
+
declaration. Type names, type-only imports and exports, labels and quoted module names hold no
|
|
122
|
+
value, so the rule skips them.
|
|
123
|
+
|
|
124
|
+
The shipped config lists the abbreviations that the code-style skill bans and turns on `id-length`
|
|
125
|
+
beside the rule, so a one-letter name fails too, other than `x`, `y`, `z`, `$` and `_`.
|
|
126
|
+
|
|
109
127
|
## `zgeoff/no-file-snapshot`
|
|
110
128
|
|
|
111
129
|
Snapshots are inline only, so the golden value sits in the test that pins it. The rule reports every
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// each `names` key is a short name and its value the text that replaces it;
|
|
2
|
+
// the rule matches a whole name, so `timeoutMs` passes when `ms` is listed
|
|
3
|
+
const noAbbreviatedNames = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'suggestion',
|
|
6
|
+
messages: {
|
|
7
|
+
abbreviated:
|
|
8
|
+
'Rename `{{name}}` to {{replacement}}: name a value for what it holds, in full words.',
|
|
9
|
+
},
|
|
10
|
+
schema: [
|
|
11
|
+
{
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
names: { type: 'object', additionalProperties: { type: 'string', minLength: 1 } },
|
|
15
|
+
},
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const names = new Map(Object.entries(context.options[0]?.names ?? {}));
|
|
22
|
+
|
|
23
|
+
if (names.size === 0) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const checkName = (node) => {
|
|
28
|
+
const name = findStaticName(node);
|
|
29
|
+
|
|
30
|
+
if (name === null || !names.has(name)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const prefix = node.type === 'PrivateIdentifier' ? '#' : '';
|
|
35
|
+
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
messageId: 'abbreviated',
|
|
39
|
+
data: { name: `${prefix}${name}`, replacement: names.get(name) },
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const checkPattern = (node) => {
|
|
44
|
+
for (const identifier of collectPatternIdentifiers(node)) {
|
|
45
|
+
checkName(identifier);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// a computed key names its property as a plain key does when it is a
|
|
50
|
+
// string written in place, as in `{ ['src']: 1 }`; a computed identifier
|
|
51
|
+
// reads a variable, whose declaration is checked where it is made
|
|
52
|
+
const checkMemberName = (node, name) => {
|
|
53
|
+
if (!node.computed || name.type !== 'Identifier') {
|
|
54
|
+
checkName(name);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const checkKey = (node) => {
|
|
59
|
+
checkMemberName(node, node.key);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const checkParams = (node) => {
|
|
63
|
+
for (const param of node.params) {
|
|
64
|
+
checkPattern(param);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// a write to a member names that property, whether the member stands
|
|
69
|
+
// alone or sits inside a destructuring target
|
|
70
|
+
const checkAssignmentTarget = (node) => {
|
|
71
|
+
for (const member of collectTargetMembers(node)) {
|
|
72
|
+
checkMemberName(member, member.property);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const checkLoopTarget = (node) => {
|
|
77
|
+
if (node.left.type !== 'VariableDeclaration') {
|
|
78
|
+
checkAssignmentTarget(node.left);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// function-verb checks only a plain function or method name, so a quoted
|
|
83
|
+
// method, a signature or a declared function would pass both rules
|
|
84
|
+
const checkFunction = (node) => {
|
|
85
|
+
checkName(node.id);
|
|
86
|
+
checkParams(node);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
VariableDeclarator(node) {
|
|
91
|
+
checkPattern(node.id);
|
|
92
|
+
},
|
|
93
|
+
FunctionDeclaration: checkFunction,
|
|
94
|
+
FunctionExpression: checkFunction,
|
|
95
|
+
ArrowFunctionExpression: checkParams,
|
|
96
|
+
|
|
97
|
+
// a signature without a body names its parameters as an implementation does
|
|
98
|
+
TSDeclareFunction: checkFunction,
|
|
99
|
+
TSEmptyBodyFunctionExpression: checkParams,
|
|
100
|
+
TSFunctionType: checkParams,
|
|
101
|
+
TSConstructorType: checkParams,
|
|
102
|
+
TSMethodSignature(node) {
|
|
103
|
+
checkKey(node);
|
|
104
|
+
checkParams(node);
|
|
105
|
+
},
|
|
106
|
+
TSCallSignatureDeclaration: checkParams,
|
|
107
|
+
TSConstructSignatureDeclaration: checkParams,
|
|
108
|
+
CatchClause(node) {
|
|
109
|
+
checkPattern(node.param);
|
|
110
|
+
},
|
|
111
|
+
'ObjectExpression > Property': checkKey,
|
|
112
|
+
PropertyDefinition: checkKey,
|
|
113
|
+
AccessorProperty: checkKey,
|
|
114
|
+
TSAbstractPropertyDefinition: checkKey,
|
|
115
|
+
TSAbstractAccessorProperty: checkKey,
|
|
116
|
+
TSPropertySignature: checkKey,
|
|
117
|
+
MethodDefinition: checkKey,
|
|
118
|
+
TSAbstractMethodDefinition: checkKey,
|
|
119
|
+
|
|
120
|
+
// a class, an enum and a namespace are values at run time; a quoted
|
|
121
|
+
// module name is a path, not a value name
|
|
122
|
+
'ClassDeclaration, ClassExpression, TSEnumDeclaration'(node) {
|
|
123
|
+
checkName(node.id);
|
|
124
|
+
},
|
|
125
|
+
TSModuleDeclaration(node) {
|
|
126
|
+
if (node.id.type === 'Identifier') {
|
|
127
|
+
checkName(node.id);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// an import's local binding is a value name in this module, whatever the
|
|
132
|
+
// exporting module calls it; a type-only import names a type, not a value
|
|
133
|
+
'ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier'(node) {
|
|
134
|
+
if (!isTypeOnlyImport(node)) {
|
|
135
|
+
checkName(node.local);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
TSImportEqualsDeclaration(node) {
|
|
139
|
+
if (node.importKind !== 'type') {
|
|
140
|
+
checkName(node.id);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
TSIndexSignature(node) {
|
|
144
|
+
for (const parameter of node.parameters) {
|
|
145
|
+
checkName(parameter);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
TSEnumMember(node) {
|
|
149
|
+
checkName(node.id);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// an export alias is the name every importing module reads the value by
|
|
153
|
+
ExportSpecifier(node) {
|
|
154
|
+
if (!isTypeOnlyExport(node)) {
|
|
155
|
+
checkName(node.exported);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
ExportAllDeclaration(node) {
|
|
159
|
+
if (node.exportKind !== 'type') {
|
|
160
|
+
checkName(node.exported);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
AssignmentExpression(node) {
|
|
164
|
+
checkAssignmentTarget(node.left);
|
|
165
|
+
},
|
|
166
|
+
ForInStatement: checkLoopTarget,
|
|
167
|
+
ForOfStatement: checkLoopTarget,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// the name a key or binding spells out in the source: an identifier, a
|
|
173
|
+
// string, or a template with no substitutions; a key built at run time has
|
|
174
|
+
// no static name, so the result is null
|
|
175
|
+
function findStaticName(node) {
|
|
176
|
+
if (node?.type === 'Identifier' || node?.type === 'PrivateIdentifier') {
|
|
177
|
+
return node.name;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (node?.type === 'Literal' && typeof node.value === 'string') {
|
|
181
|
+
return node.value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
185
|
+
return node.quasis[0].value.cooked ?? null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isTypeOnlyImport(specifier) {
|
|
192
|
+
return specifier.importKind === 'type' || specifier.parent.importKind === 'type';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isTypeOnlyExport(specifier) {
|
|
196
|
+
return specifier.exportKind === 'type' || specifier.parent.exportKind === 'type';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// the members an assignment target writes; a bare identifier in the target
|
|
200
|
+
// writes a variable declared elsewhere, and its declaration is checked there
|
|
201
|
+
function collectTargetMembers(node) {
|
|
202
|
+
if (node === null || node === undefined) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (node.type === 'MemberExpression') {
|
|
207
|
+
return [node];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (node.type === 'ObjectPattern') {
|
|
211
|
+
return node.properties.flatMap((property) => {
|
|
212
|
+
const target = property.type === 'RestElement' ? property : property.value;
|
|
213
|
+
|
|
214
|
+
return collectTargetMembers(target);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (node.type === 'ArrayPattern') {
|
|
219
|
+
return node.elements.flatMap((element) => collectTargetMembers(element));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (node.type === 'AssignmentPattern') {
|
|
223
|
+
return collectTargetMembers(node.left);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (node.type === 'RestElement') {
|
|
227
|
+
return collectTargetMembers(node.argument);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// the names a binding pattern declares: a destructured key's alias, not the
|
|
234
|
+
// key, since the key belongs to the object being read
|
|
235
|
+
function collectPatternIdentifiers(node) {
|
|
236
|
+
if (node === null || node === undefined) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (node.type === 'Identifier') {
|
|
241
|
+
return [node];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (node.type === 'ObjectPattern') {
|
|
245
|
+
return node.properties.flatMap((property) => {
|
|
246
|
+
const binding = property.type === 'RestElement' ? property : property.value;
|
|
247
|
+
|
|
248
|
+
return collectPatternIdentifiers(binding);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (node.type === 'ArrayPattern') {
|
|
253
|
+
return node.elements.flatMap((element) => collectPatternIdentifiers(element));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (node.type === 'AssignmentPattern') {
|
|
257
|
+
return collectPatternIdentifiers(node.left);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (node.type === 'RestElement') {
|
|
261
|
+
return collectPatternIdentifiers(node.argument);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (node.type === 'TSParameterProperty') {
|
|
265
|
+
return collectPatternIdentifiers(node.parameter);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export default noAbbreviatedNames;
|
package/oxlintrc.json
CHANGED
|
@@ -33,6 +33,35 @@
|
|
|
33
33
|
"zgeoff/test-beside-module": "error",
|
|
34
34
|
"zgeoff/no-file-snapshot": "error",
|
|
35
35
|
|
|
36
|
+
// name a value for what it holds, in full words: one letter only for a
|
|
37
|
+
// coordinate, zod's `z`, Bun's shell `$` or an unread `_` placeholder, and
|
|
38
|
+
// each listed abbreviation reported with its full word; both rules read
|
|
39
|
+
// every binding (destructured aliases and imports included) and every
|
|
40
|
+
// property key, but a type parameter is not a value, so generics stay
|
|
41
|
+
// unchecked
|
|
42
|
+
"id-length": [
|
|
43
|
+
"error",
|
|
44
|
+
{ "min": 2, "exceptions": ["x", "y", "z", "$", "_"], "checkGeneric": false }
|
|
45
|
+
],
|
|
46
|
+
// oxlintrc.test.ts holds the same names with their full words, and fails
|
|
47
|
+
// when an entry changes here without it
|
|
48
|
+
"zgeoff/no-abbreviated-names": [
|
|
49
|
+
"error",
|
|
50
|
+
{
|
|
51
|
+
"names": {
|
|
52
|
+
"mgr": "`manager`",
|
|
53
|
+
"dir": "`directory`",
|
|
54
|
+
"tmp": "`temporary`",
|
|
55
|
+
"proc": "`process`",
|
|
56
|
+
"src": "`source`",
|
|
57
|
+
"dest": "`destination`",
|
|
58
|
+
"prev": "`previous`",
|
|
59
|
+
"msg": "`message`",
|
|
60
|
+
"ms": "a name for the value with `Ms` as its unit suffix, such as `timeoutMs`"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
|
|
36
65
|
// enforced comment style — line comments unless JSDoc or exclamation
|
|
37
66
|
"@stylistic/multiline-comment-style": [
|
|
38
67
|
"error",
|
|
@@ -114,14 +143,13 @@
|
|
|
114
143
|
// category-enabled rules that contradict this config's style: inline named
|
|
115
144
|
// export declarations are the convention (group-exports demands the exact
|
|
116
145
|
// form no-bare-named-exports bans), keys group by meaning not alphabet,
|
|
117
|
-
// comments stay lowercase,
|
|
146
|
+
// comments stay lowercase, and literal numbers are fine
|
|
118
147
|
"import/prefer-default-export": "off",
|
|
119
148
|
"import/no-named-export": "off",
|
|
120
149
|
"import/exports-last": "off",
|
|
121
150
|
"import/group-exports": "off",
|
|
122
151
|
"sort-keys": "off",
|
|
123
152
|
"capitalized-comments": "off",
|
|
124
|
-
"id-length": "off",
|
|
125
153
|
"no-magic-numbers": "off",
|
|
126
154
|
// prefer-ternary (only-single-line, above) wants ternaries where they fit
|
|
127
155
|
// on one line; no-ternary would ban the same code
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zgeoff/oxlint-config",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Shareable oxlint config: strict categories, house-style rules, and a custom JS plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"plugin.js",
|
|
14
14
|
"function-verb.js",
|
|
15
15
|
"no-restricted-words.js",
|
|
16
|
+
"no-abbreviated-names.js",
|
|
16
17
|
"max-consecutive-line-comments.js",
|
|
17
18
|
"no-jsdoc.js",
|
|
18
19
|
"no-comment-rule-override.js",
|
package/plugin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import functionVerb from './function-verb.js';
|
|
2
2
|
import importZones from './import-zones.js';
|
|
3
3
|
import maxConsecutiveLineComments from './max-consecutive-line-comments.js';
|
|
4
|
+
import noAbbreviatedNames from './no-abbreviated-names.js';
|
|
4
5
|
import noCommentRuleOverride from './no-comment-rule-override.js';
|
|
5
6
|
import noFileSnapshot from './no-file-snapshot.js';
|
|
6
7
|
import noJSDoc from './no-jsdoc.js';
|
|
@@ -107,6 +108,7 @@ const plugin = {
|
|
|
107
108
|
'no-comment-rule-override': noCommentRuleOverride,
|
|
108
109
|
'function-verb': functionVerb,
|
|
109
110
|
'no-restricted-words': noRestrictedWords,
|
|
111
|
+
'no-abbreviated-names': noAbbreviatedNames,
|
|
110
112
|
'test-beside-module': testBesideModule,
|
|
111
113
|
'no-file-snapshot': noFileSnapshot,
|
|
112
114
|
'import-zones': importZones,
|