@zgeoff/oxlint-config 0.2.9 → 0.2.10

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 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, and
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,15 @@ 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 a
113
+ variable, parameter, catch parameter, destructured name, import, property, getter or setter that has
114
+ one of those names. A name matches whole, so `ms` reports `ms` but not `timeoutMs`. A destructured
115
+ key that an alias renames belongs to the object being read, and the rule reports only the alias. The
116
+ shipped config lists the abbreviations that the code-style skill bans and turns on `id-length`
117
+ beside the rule, so a one-letter name fails too, other than `x`, `y`, `z`, `$` and `_`.
118
+
109
119
  ## `zgeoff/no-file-snapshot`
110
120
 
111
121
  Snapshots are inline only, so the golden value sits in the test that pins it. The rule reports every
@@ -0,0 +1,159 @@
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 isName = node?.type === 'Identifier' || node?.type === 'PrivateIdentifier';
29
+
30
+ if (!isName || !names.has(node.name)) {
31
+ return;
32
+ }
33
+
34
+ const prefix = node.type === 'PrivateIdentifier' ? '#' : '';
35
+
36
+ context.report({
37
+ node,
38
+ messageId: 'abbreviated',
39
+ data: { name: `${prefix}${node.name}`, replacement: names.get(node.name) },
40
+ });
41
+ };
42
+
43
+ const checkPattern = (node) => {
44
+ for (const identifier of collectPatternIdentifiers(node)) {
45
+ checkName(identifier);
46
+ }
47
+ };
48
+
49
+ const checkKey = (node) => {
50
+ if (!node.computed) {
51
+ checkName(node.key);
52
+ }
53
+ };
54
+
55
+ const checkParams = (node) => {
56
+ for (const param of node.params) {
57
+ checkPattern(param);
58
+ }
59
+ };
60
+
61
+ return {
62
+ VariableDeclarator(node) {
63
+ checkPattern(node.id);
64
+ },
65
+ FunctionDeclaration: checkParams,
66
+ FunctionExpression: checkParams,
67
+ ArrowFunctionExpression: checkParams,
68
+
69
+ // a signature without a body names its parameters as an implementation does
70
+ TSDeclareFunction: checkParams,
71
+ TSEmptyBodyFunctionExpression: checkParams,
72
+ TSFunctionType: checkParams,
73
+ TSConstructorType: checkParams,
74
+ TSMethodSignature: checkParams,
75
+ TSCallSignatureDeclaration: checkParams,
76
+ TSConstructSignatureDeclaration: checkParams,
77
+ CatchClause(node) {
78
+ checkPattern(node.param);
79
+ },
80
+ 'ObjectExpression > Property': checkKey,
81
+ PropertyDefinition: checkKey,
82
+ AccessorProperty: checkKey,
83
+ TSAbstractPropertyDefinition: checkKey,
84
+ TSAbstractAccessorProperty: checkKey,
85
+ TSPropertySignature: checkKey,
86
+
87
+ // a getter or setter is read and written as a property, so its key is a
88
+ // value name
89
+ "MethodDefinition[kind='get']": checkKey,
90
+ "MethodDefinition[kind='set']": checkKey,
91
+ "TSAbstractMethodDefinition[kind='get']": checkKey,
92
+ "TSAbstractMethodDefinition[kind='set']": checkKey,
93
+ "TSMethodSignature[kind='get']": checkKey,
94
+ "TSMethodSignature[kind='set']": checkKey,
95
+
96
+ // an import's local binding is a value name in this module, whatever the
97
+ // exporting module calls it; a type-only import names a type, not a value
98
+ 'ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier'(node) {
99
+ if (!isTypeOnlyImport(node)) {
100
+ checkName(node.local);
101
+ }
102
+ },
103
+ TSImportEqualsDeclaration(node) {
104
+ if (node.importKind !== 'type') {
105
+ checkName(node.id);
106
+ }
107
+ },
108
+ 'AssignmentExpression > MemberExpression.left'(node) {
109
+ if (!node.computed) {
110
+ checkName(node.property);
111
+ }
112
+ },
113
+ };
114
+ },
115
+ };
116
+
117
+ function isTypeOnlyImport(specifier) {
118
+ return specifier.importKind === 'type' || specifier.parent.importKind === 'type';
119
+ }
120
+
121
+ // the names a binding pattern declares: a destructured key's alias, not the
122
+ // key, since the key belongs to the object being read
123
+ function collectPatternIdentifiers(node) {
124
+ if (node === null || node === undefined) {
125
+ return [];
126
+ }
127
+
128
+ if (node.type === 'Identifier') {
129
+ return [node];
130
+ }
131
+
132
+ if (node.type === 'ObjectPattern') {
133
+ return node.properties.flatMap((property) => {
134
+ const binding = property.type === 'RestElement' ? property : property.value;
135
+
136
+ return collectPatternIdentifiers(binding);
137
+ });
138
+ }
139
+
140
+ if (node.type === 'ArrayPattern') {
141
+ return node.elements.flatMap((element) => collectPatternIdentifiers(element));
142
+ }
143
+
144
+ if (node.type === 'AssignmentPattern') {
145
+ return collectPatternIdentifiers(node.left);
146
+ }
147
+
148
+ if (node.type === 'RestElement') {
149
+ return collectPatternIdentifiers(node.argument);
150
+ }
151
+
152
+ if (node.type === 'TSParameterProperty') {
153
+ return collectPatternIdentifiers(node.parameter);
154
+ }
155
+
156
+ return [];
157
+ }
158
+
159
+ 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, short identifiers and literal numbers are fine
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.9",
3
+ "version": "0.2.10",
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,