@reticlehq/eslint-plugin 2.13.1 → 2.14.0
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/dist/index.d.ts +2 -2
- package/dist/no-internal-tags.d.ts +8 -3
- package/dist/no-internal-tags.js +66 -4
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const rules: {
|
|
|
5
5
|
readonly "require-signal-on-mutation": import("@typescript-eslint/utils/ts-eslint").RuleModule<"mutationWithoutSignal", [import("./options.js").RawRuleOptions], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
6
6
|
name: string;
|
|
7
7
|
};
|
|
8
|
-
readonly "no-internal-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"internalTag", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
8
|
+
readonly "no-internal-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"internalTag" | "pathTag", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
9
9
|
name: string;
|
|
10
10
|
};
|
|
11
11
|
};
|
|
@@ -17,7 +17,7 @@ declare const plugin: {
|
|
|
17
17
|
readonly "require-signal-on-mutation": import("@typescript-eslint/utils/ts-eslint").RuleModule<"mutationWithoutSignal", [import("./options.js").RawRuleOptions], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
18
18
|
name: string;
|
|
19
19
|
};
|
|
20
|
-
readonly "no-internal-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"internalTag", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
20
|
+
readonly "no-internal-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"internalTag" | "pathTag", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
21
21
|
name: string;
|
|
22
22
|
};
|
|
23
23
|
};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rule: no-internal-tags.
|
|
3
3
|
*
|
|
4
|
-
* Bans design-doc reference codes and internal version strings from comments,
|
|
5
|
-
*
|
|
4
|
+
* Bans design-doc reference codes and internal version strings from comments, TEST DESCRIPTIONS and
|
|
5
|
+
* the FILE AND DIRECTORY NAMES of the module itself, so a reader of the source never meets a token
|
|
6
|
+
* that only means something in a document they cannot see.
|
|
7
|
+
*
|
|
8
|
+
* Those four surfaces are the ones the project rule names, and the rule enforced two of them. A path
|
|
9
|
+
* is the first thing a reader meets, before any comment, so `n5-ring-buffer.ts` is the shape this
|
|
10
|
+
* exists to prevent stated in the loudest available place.
|
|
6
11
|
*
|
|
7
12
|
* This rule exists because the prose version of it did not hold. An audit of the codebase found the
|
|
8
13
|
* machine-checked rules at ~100% compliance and every prose-only rule violated — 59 tracking codes
|
|
@@ -18,6 +23,6 @@
|
|
|
18
23
|
* `H2`, `Welford M2` when part of a sentence) is matched only when it stands alone as a tag.
|
|
19
24
|
*/
|
|
20
25
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
21
|
-
export declare const noInternalTags: ESLintUtils.RuleModule<"internalTag", [], unknown, ESLintUtils.RuleListener> & {
|
|
26
|
+
export declare const noInternalTags: ESLintUtils.RuleModule<"internalTag" | "pathTag", [], unknown, ESLintUtils.RuleListener> & {
|
|
22
27
|
name: string;
|
|
23
28
|
};
|
package/dist/no-internal-tags.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rule: no-internal-tags.
|
|
3
3
|
*
|
|
4
|
-
* Bans design-doc reference codes and internal version strings from comments,
|
|
5
|
-
*
|
|
4
|
+
* Bans design-doc reference codes and internal version strings from comments, TEST DESCRIPTIONS and
|
|
5
|
+
* the FILE AND DIRECTORY NAMES of the module itself, so a reader of the source never meets a token
|
|
6
|
+
* that only means something in a document they cannot see.
|
|
7
|
+
*
|
|
8
|
+
* Those four surfaces are the ones the project rule names, and the rule enforced two of them. A path
|
|
9
|
+
* is the first thing a reader meets, before any comment, so `n5-ring-buffer.ts` is the shape this
|
|
10
|
+
* exists to prevent stated in the loudest available place.
|
|
6
11
|
*
|
|
7
12
|
* This rule exists because the prose version of it did not hold. An audit of the codebase found the
|
|
8
13
|
* machine-checked rules at ~100% compliance and every prose-only rule violated — 59 tracking codes
|
|
@@ -17,10 +22,12 @@
|
|
|
17
22
|
* Ordinary technical prose that happens to contain a letter and digits inside a larger word (`UTF-8`,
|
|
18
23
|
* `H2`, `Welford M2` when part of a sentence) is matched only when it stands alone as a tag.
|
|
19
24
|
*/
|
|
25
|
+
import { relative, sep } from 'node:path';
|
|
20
26
|
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
21
27
|
import { DOCS_URL_ROOT } from './constants.js';
|
|
22
28
|
const createRule = ESLintUtils.RuleCreator((name) => `${DOCS_URL_ROOT}#${name}`);
|
|
23
29
|
const INTERNAL_TAG_MESSAGE = "Internal reference '{{tag}}' does not belong in source. It means nothing to a reader without the design doc — describe the behaviour instead.";
|
|
30
|
+
const PATH_TAG_MESSAGE = "Internal reference '{{tag}}' does not belong in a file or directory name. It means nothing to a reader without the design doc — name the module after what it does.";
|
|
24
31
|
/** `(W11)`, `B37`, `W10.3`, `N5:` — a short capital-prefixed code used as a label. */
|
|
25
32
|
const TRACKING_CODE = /(?<![A-Za-z0-9])[A-Z]{1,2}\d{1,2}(?:\.\d{1,2})?(?![A-Za-z0-9])/g;
|
|
26
33
|
/**
|
|
@@ -41,6 +48,16 @@ const EXTERNAL_SPECS = /\b(RFC|WCAG|ECMA|ISO|IEEE|W3C|WHATWG|HTML|CSS|ARIA|HTTP|
|
|
|
41
48
|
* banned string legal.
|
|
42
49
|
*/
|
|
43
50
|
const VERSION_STRING = /(?<![A-Za-z0-9])v\d+\.\d+\.\d+(?![A-Za-z0-9])/g;
|
|
51
|
+
/**
|
|
52
|
+
* The same two shapes over a PATH, case-insensitively.
|
|
53
|
+
*
|
|
54
|
+
* File names here are kebab-case, so a code that would read `N5` in a sentence reads `n5` in a path
|
|
55
|
+
* and the upper-case patterns above cannot see it. The boundaries do the work that case was doing:
|
|
56
|
+
* `e2e`, `utf8`, `http2` and `base64` all fail them, because each needs a non-alphanumeric on both
|
|
57
|
+
* sides of a letters-then-digits token.
|
|
58
|
+
*/
|
|
59
|
+
const PATH_TRACKING_CODE = /(?<![A-Za-z0-9])[A-Za-z]{1,2}\d{1,2}(?:\.\d{1,2})?(?![A-Za-z0-9])/g;
|
|
60
|
+
const PATH_VERSION_STRING = /(?<![A-Za-z0-9])v\d+\.\d+\.\d+(?![A-Za-z0-9])/gi;
|
|
44
61
|
/** Third-party names whose versions are legitimate prose. Reticle is deliberately NOT here. */
|
|
45
62
|
const THIRD_PARTY = /\b(React|Vite|Node|TypeScript|Playwright|Chrome|Chromium|Firefox|Safari|Zod|Vitest|ESLint|pnpm|MCP)\b[^v]{0,12}$/i;
|
|
46
63
|
/** Tokens that look like codes but are established technical terms. */
|
|
@@ -83,7 +100,7 @@ export const noInternalTags = createRule({
|
|
|
83
100
|
type: 'problem',
|
|
84
101
|
docs: { description: 'Ban design-doc codes and internal version strings from comments.' },
|
|
85
102
|
schema: [],
|
|
86
|
-
messages: { internalTag: INTERNAL_TAG_MESSAGE },
|
|
103
|
+
messages: { internalTag: INTERNAL_TAG_MESSAGE, pathTag: PATH_TAG_MESSAGE },
|
|
87
104
|
},
|
|
88
105
|
defaultOptions: [],
|
|
89
106
|
create(context) {
|
|
@@ -109,13 +126,58 @@ export const noInternalTags = createRule({
|
|
|
109
126
|
}
|
|
110
127
|
return found;
|
|
111
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Banned tokens in this module's own path, relative to the project root.
|
|
131
|
+
*
|
|
132
|
+
* Relative, never absolute: an absolute path runs through directories nobody chose as part of
|
|
133
|
+
* this codebase (a CI runner's workspace, a developer's home) and flagging those would report a
|
|
134
|
+
* violation the author cannot fix.
|
|
135
|
+
*/
|
|
136
|
+
const pathTags = () => {
|
|
137
|
+
const found = new Set();
|
|
138
|
+
const filename = context.filename;
|
|
139
|
+
// `<input>` and `<text>` are what a linter reports for source with no file, e.g. a RuleTester
|
|
140
|
+
// case or a piped snippet. There is no path to judge.
|
|
141
|
+
if (filename.startsWith('<'))
|
|
142
|
+
return found;
|
|
143
|
+
const rel = relative(context.cwd, filename);
|
|
144
|
+
// Outside the project root entirely: `relative` climbs out with `..`, and the segments above
|
|
145
|
+
// the root are not this codebase's to answer for.
|
|
146
|
+
if (rel.startsWith('..'))
|
|
147
|
+
return found;
|
|
148
|
+
const path = rel.split(sep).join('/');
|
|
149
|
+
// Version FIRST, and its spans claimed, because the tracking-code shape is a prefix of it:
|
|
150
|
+
// `replay-v2.2.0.ts` matches `v2.2.0` as a version and `v2.2` as a code, and reporting both
|
|
151
|
+
// would name the same offence twice under two different explanations.
|
|
152
|
+
const claimed = [];
|
|
153
|
+
for (const pattern of [PATH_VERSION_STRING, PATH_TRACKING_CODE]) {
|
|
154
|
+
pattern.lastIndex = 0;
|
|
155
|
+
for (const match of path.matchAll(pattern)) {
|
|
156
|
+
const start = match.index ?? 0;
|
|
157
|
+
const end = start + match[0].length;
|
|
158
|
+
if (claimed.some((c) => start < c.end && end > c.start))
|
|
159
|
+
continue;
|
|
160
|
+
const tag = match[0].trim();
|
|
161
|
+
claimed.push({ start, end });
|
|
162
|
+
if (ALLOWED.has(tag.replace(/[^A-Za-z0-9]/g, '').toUpperCase()))
|
|
163
|
+
continue;
|
|
164
|
+
found.add(tag);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return found;
|
|
168
|
+
};
|
|
112
169
|
return {
|
|
113
|
-
Program() {
|
|
170
|
+
Program(node) {
|
|
114
171
|
for (const comment of context.sourceCode.getAllComments()) {
|
|
115
172
|
for (const tag of tagsIn(comment.value)) {
|
|
116
173
|
context.report({ node: comment, messageId: 'internalTag', data: { tag } });
|
|
117
174
|
}
|
|
118
175
|
}
|
|
176
|
+
// Reported on the Program node: the offence is the module's name, which has no node of its
|
|
177
|
+
// own, and the first line is where a reader will look for it.
|
|
178
|
+
for (const tag of pathTags()) {
|
|
179
|
+
context.report({ node, messageId: 'pathTag', data: { tag } });
|
|
180
|
+
}
|
|
119
181
|
},
|
|
120
182
|
/**
|
|
121
183
|
* TEST DESCRIPTIONS are string literals, not comments, so a comment-only rule could not see them
|
package/package.json
CHANGED