@ethisyscore/eslint-plugin-coreconnect 1.71.5 → 1.72.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethisyscore/eslint-plugin-coreconnect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.72.0",
|
|
4
4
|
"description": "ESLint rules enforcing EthisysCore plugin frontend conventions. Published so a new rule reaches every plugin on a version bump, rather than being copied into each scaffold and drifting.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
*/
|
|
10
10
|
const COLOR_REGEX = /^#(?:[0-9a-fA-F]{3}){1,2}(?:[0-9a-fA-F]{2})?$|^rgba?\(|^hsla?\(/;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A colour function whose channels come from CSS custom properties - `hsl(var(--border))`,
|
|
14
|
+
* `rgb(var(--cc-accent) / 0.5)`.
|
|
15
|
+
*
|
|
16
|
+
* This is not a hardcoded colour, it is the exact thing the rule's own message asks for, and
|
|
17
|
+
* flagging it was a false positive: `COLOR_REGEX` matches on the `hsl(` prefix alone and never
|
|
18
|
+
* looked inside. Measured on `finance`, whose `src/styles/tailwind.config.ts` is written entirely in
|
|
19
|
+
* this form - 48 findings, every one of them already themed, which is enough noise to teach a reader
|
|
20
|
+
* that the rule is wrong rather than that the file is.
|
|
21
|
+
*/
|
|
22
|
+
const TOKENISED_COLOR_REGEX = /^(?:rgba?|hsla?)\(\s*var\(\s*--/;
|
|
23
|
+
|
|
12
24
|
export default {
|
|
13
25
|
meta: {
|
|
14
26
|
type: "suggestion",
|
|
@@ -28,7 +40,15 @@ export default {
|
|
|
28
40
|
if (typeof node.value !== "string") {
|
|
29
41
|
return;
|
|
30
42
|
}
|
|
31
|
-
|
|
43
|
+
const value = node.value.trim();
|
|
44
|
+
|
|
45
|
+
// Order matters: a tokenised colour matches COLOR_REGEX too, so the exemption has to be
|
|
46
|
+
// checked first.
|
|
47
|
+
if (TOKENISED_COLOR_REGEX.test(value)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (COLOR_REGEX.test(value)) {
|
|
32
52
|
context.report({ node, messageId: "noHardcodedColor", data: { value: node.value } });
|
|
33
53
|
}
|
|
34
54
|
},
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* @type {import("eslint").Rule.RuleModule}
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { isTestFile } from "./testFiles.js";
|
|
17
|
+
|
|
16
18
|
const FUNCTION_LIKE = new Set([
|
|
17
19
|
"FunctionDeclaration",
|
|
18
20
|
"FunctionExpression",
|
|
@@ -96,6 +98,11 @@ export default {
|
|
|
96
98
|
schema: [],
|
|
97
99
|
},
|
|
98
100
|
create(context) {
|
|
101
|
+
// Test scaffolding is exempt: see isTestFile for why this rule's rationale does not reach it.
|
|
102
|
+
if (isTestFile(context.filename ?? context.getFilename?.())) {
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
return {
|
|
100
107
|
TSTypeLiteral(node) {
|
|
101
108
|
const position = positionOf(node);
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* @type {import("eslint").Rule.RuleModule}
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { isTestFile } from "./testFiles.js";
|
|
16
|
+
|
|
15
17
|
const MINIMUM_MEMBERS = 3;
|
|
16
18
|
|
|
17
19
|
/** A `"literal"` type node, across the parser versions that spell it differently. */
|
|
@@ -37,6 +39,11 @@ export default {
|
|
|
37
39
|
schema: [],
|
|
38
40
|
},
|
|
39
41
|
create(context) {
|
|
42
|
+
// Test scaffolding is exempt: see isTestFile for why this rule's rationale does not reach it.
|
|
43
|
+
if (isTestFile(context.filename ?? context.getFilename?.())) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
return {
|
|
41
48
|
TSUnionType(node) {
|
|
42
49
|
const members = node.types ?? [];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a file is test scaffolding rather than shipped code.
|
|
3
|
+
*
|
|
4
|
+
* Used by the two STRUCTURAL rules - `no-inline-object-type` and `no-inline-string-union` - and by
|
|
5
|
+
* nothing else. Both exist to stop an unnamed shape being re-written slightly differently by the
|
|
6
|
+
* next person who needs it, and that argument does not reach a test file: the shape belongs to one
|
|
7
|
+
* mock in one file, nobody imports it, and it is deleted when the test is.
|
|
8
|
+
*
|
|
9
|
+
* Measured before adding this: 80 of the estate's convention findings sit in test files, 74 of them
|
|
10
|
+
* `no-inline-object-type`, and almost all are the prop type of a hand-rolled MUI stub -
|
|
11
|
+
* `Alert: ({ children, action }: { children?: ReactNode; action?: ReactNode }) => ...`. Naming those
|
|
12
|
+
* 74 would add 74 aliases that exist only to satisfy a rule, in files where the rule's own rationale
|
|
13
|
+
* does not apply. A rule that demands ceremony gets switched off, which costs the 679 findings in
|
|
14
|
+
* shipped code that it is genuinely right about.
|
|
15
|
+
*
|
|
16
|
+
* ## What counts, and why a basename never can
|
|
17
|
+
*
|
|
18
|
+
* Only the three markers below, all of which say "this file exists to test something":
|
|
19
|
+
* a `__tests__/` or `__mocks__/` directory, or a `.test.` / `.spec.` suffix. They are conventions a
|
|
20
|
+
* bundler, a test runner and a reader all already agree on.
|
|
21
|
+
*
|
|
22
|
+
* A fourth marker was tried and WITHDRAWN: a basename match on `Mock[A-Z]*`, meant to catch "a
|
|
23
|
+
* hand-rolled stub kept beside the code it doubles rather than under `__tests__`". It was wrong,
|
|
24
|
+
* and wrong in the direction that costs most - it exempted SHIPPED code.
|
|
25
|
+
*
|
|
26
|
+
* Every plugin in this estate has `src/services/implementations/mock/Mock<Domain>Service.ts`, and
|
|
27
|
+
* those are not scaffolding. `useProjectService.ts` imports `MockProjectService` statically and
|
|
28
|
+
* picks between it and the real service at runtime on `IS_MOCK_MODE`, so the class is in the
|
|
29
|
+
* production bundle and its shapes are read by the same hooks that read the real service's. The
|
|
30
|
+
* rule's rationale reaches them exactly as it reaches any other source file.
|
|
31
|
+
*
|
|
32
|
+
* Measured on 2026-08-29 across the eight lint-adopting plugins: the basename branch was the ONLY
|
|
33
|
+
* thing exempting 17 findings, and every one of them was in a shipped `Mock*Service.ts` under a
|
|
34
|
+
* `mock/` directory. Nothing legitimate depended on it - the hand-rolled MUI stubs the exemption
|
|
35
|
+
* was written for all sit under `__tests__/` or carry a `.test.` suffix, and stay exempt.
|
|
36
|
+
*
|
|
37
|
+
* Two lessons worth keeping: a heuristic on a NAME cannot tell a test double from a shipped
|
|
38
|
+
* implementation of the same interface, because both are named after what they double; and an
|
|
39
|
+
* exemption that silently widens is worse than a finding, because nothing reports it.
|
|
40
|
+
*
|
|
41
|
+
* ## Why the check lives here
|
|
42
|
+
*
|
|
43
|
+
* The glob-shaped alternative - a `files` override in `configs.recommended` - would require that
|
|
44
|
+
* config to become an array, and every consumer spreads it as an object
|
|
45
|
+
* (`{ files: [...], ...coreconnect.configs.recommended }`). Changing that shape would break nine
|
|
46
|
+
* repos at once, so the check lives in the rules, which is also how this package delivers everything
|
|
47
|
+
* else: on a version bump, with no consumer edit.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately NOT applied to `no-hardcoded-colors` or to any of the `no-local-*` rules. A hardcoded
|
|
50
|
+
* colour is trivially fixable anywhere and there is exactly one in a test file estate-wide; and a
|
|
51
|
+
* test file that re-declares the shared data grid is exactly the drift those rules exist to catch.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} filename Absolute or relative path, as ESLint reports it.
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
export function isTestFile(filename) {
|
|
57
|
+
if (!filename) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Normalise Windows separators: ESLint reports native paths, and the estate is developed on both.
|
|
62
|
+
const path = filename.replace(/\\/g, "/");
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
/(^|\/)__tests__\//.test(path) ||
|
|
66
|
+
/(^|\/)__mocks__\//.test(path) ||
|
|
67
|
+
/\.(test|spec)\.[cm]?[jt]sx?$/.test(path)
|
|
68
|
+
);
|
|
69
|
+
}
|