@starci/eslint-canon-fe 1.3.1 → 1.4.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/file-layout.mjs +33 -3
- package/naming.mjs +36 -7
- package/package.json +1 -1
package/file-layout.mjs
CHANGED
|
@@ -22,7 +22,7 @@ const TWO_FILE_TIERS = /\/src\/components\/(pages|layouts)\/([^/]+)\/(.+)$/
|
|
|
22
22
|
const TWO_FILE_TIERS_WITH_CATEGORY = /\/src\/components\/(overlays)\/[^/]+\/([^/]+)\/(.+)$/
|
|
23
23
|
|
|
24
24
|
/** The two halves, and the twin test of each, are the whole of what a surface folder may hold. */
|
|
25
|
-
const ALLOWED_IN_SURFACE_FOLDER = /^(?:component|index)(?:\.
|
|
25
|
+
const ALLOWED_IN_SURFACE_FOLDER = /^(?:component|index)(?:\.spec)?\.tsx?$/
|
|
26
26
|
|
|
27
27
|
/** Folders that are not component code, whatever they are nested inside. */
|
|
28
28
|
const NON_COMPONENT_FOLDERS = /\/src\/components\/.*\/(constants|utils|types|hooks)\//
|
|
@@ -240,9 +240,9 @@ const ROUTE_TREE_EXEMPT = /^(?:api\/|_)/
|
|
|
240
240
|
*
|
|
241
241
|
* The name is deliberately not required to match `page` or `layout`. A route's tests split by
|
|
242
242
|
* CONCERN - what the screen renders, who may reach it, where the boundary sits - and forcing them
|
|
243
|
-
* into one `page.
|
|
243
|
+
* into one `page.spec.tsx` would buy nothing but a longer file.
|
|
244
244
|
*/
|
|
245
|
-
const ROUTE_TREE_TEST = /\.
|
|
245
|
+
const ROUTE_TREE_TEST = /\.spec\.(?:tsx?|jsx?)$/
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
248
|
* A file under `app/` names which page renders at which URL, and does nothing else.
|
|
@@ -362,6 +362,35 @@ export const noShellTier = {
|
|
|
362
362
|
},
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// -- FILE-9 --------------------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
const UNIT_TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$/
|
|
368
|
+
const SEPARATE_FRONTEND_TEST_TREE = /\/(?:src\/tests|tests?\/unit|e2e)(?:\/|$)/
|
|
369
|
+
|
|
370
|
+
/** Frontend units are `.spec.` twins beside the source they exercise. */
|
|
371
|
+
export const unitTestColocated = {
|
|
372
|
+
meta: {
|
|
373
|
+
type: "problem",
|
|
374
|
+
docs: { description: "Frontend unit tests are colocated `.spec.` files; frontend owns no separate test tree." },
|
|
375
|
+
schema: [],
|
|
376
|
+
messages: {
|
|
377
|
+
suffix: "`{{name}}` uses the generic `.test.` suffix. A frontend unit is a colocated `.spec.` twin beside its production owner.",
|
|
378
|
+
bucket: "`{{path}}` files a frontend unit in a separate test bucket. Move the cases beside their production owner; only backend E2E owns a separate test tree.",
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
create(context) {
|
|
382
|
+
const file = normalizePath(context.filename || context.getFilename())
|
|
383
|
+
const match = file.match(UNIT_TEST_FILE)
|
|
384
|
+
if (!match) return {}
|
|
385
|
+
return {
|
|
386
|
+
Program(node) {
|
|
387
|
+
if (match[1] === "test") context.report({ node, messageId: "suffix", data: { name: file.slice(file.lastIndexOf("/") + 1) } })
|
|
388
|
+
if (SEPARATE_FRONTEND_TEST_TREE.test(file)) context.report({ node, messageId: "bucket", data: { path: file } })
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
|
|
365
394
|
export const rules = {
|
|
366
395
|
"no-shell-tier": noShellTier,
|
|
367
396
|
"source-tier-marker-matches-folder": sourceTierMarkerMatchesFolder,
|
|
@@ -371,6 +400,7 @@ export const rules = {
|
|
|
371
400
|
"export-matches-folder": exportMatchesFolder,
|
|
372
401
|
"no-runtime-namespace": noRuntimeNamespace,
|
|
373
402
|
"monorepo-tier-belongs-to-its-side": monorepoTierBelongsToItsSide,
|
|
403
|
+
"unit-test-colocated": unitTestColocated,
|
|
374
404
|
}
|
|
375
405
|
|
|
376
406
|
/**
|
package/naming.mjs
CHANGED
|
@@ -44,7 +44,7 @@ export const preferArrowExport = {
|
|
|
44
44
|
// -- NAMING-2 --------------------------------------------------------------------------------------
|
|
45
45
|
|
|
46
46
|
/** Something a reader triggers is named `onX`, so the name survives being passed. */
|
|
47
|
-
export const handlerOnPrefix = {
|
|
47
|
+
export const handlerOnPrefix = {
|
|
48
48
|
meta: {
|
|
49
49
|
type: "suggestion",
|
|
50
50
|
docs: { description: "Handlers are named `onX`, not `handleX`." },
|
|
@@ -70,7 +70,35 @@ export const handlerOnPrefix = {
|
|
|
70
70
|
},
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
-
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// -- machine-only identity -------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/** A const introduces a value; it does not merely give an existing identifier a second name. */
|
|
78
|
+
export const noDirectConstAlias = {
|
|
79
|
+
meta: {
|
|
80
|
+
type: "problem",
|
|
81
|
+
docs: { description: "A const must not be a direct identifier-to-identifier alias." },
|
|
82
|
+
schema: [],
|
|
83
|
+
messages: {
|
|
84
|
+
alias:
|
|
85
|
+
"`const {{alias}} = {{original}}` gives one value two names without adding behavior. Use `{{original}}` directly, or make `{{alias}}` the result of the transformation it claims to represent.",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
create(context) {
|
|
89
|
+
return {
|
|
90
|
+
VariableDeclarator(node) {
|
|
91
|
+
if (node.parent?.kind !== "const") return
|
|
92
|
+
if (node.id?.type !== "Identifier" || node.init?.type !== "Identifier") return
|
|
93
|
+
context.report({
|
|
94
|
+
node: node.id,
|
|
95
|
+
messageId: "alias",
|
|
96
|
+
data: { alias: node.id.name, original: node.init.name },
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
}
|
|
74
102
|
|
|
75
103
|
/** The rules this law contributes to the plugin. */
|
|
76
104
|
// -- NAMING-3 --------------------------------------------------------------------------------------
|
|
@@ -134,11 +162,12 @@ export const noSecondLanguageInPath = {
|
|
|
134
162
|
},
|
|
135
163
|
}
|
|
136
164
|
|
|
137
|
-
export const rules = {
|
|
138
|
-
"prefer-arrow-export": preferArrowExport,
|
|
139
|
-
"handler-on-prefix": handlerOnPrefix,
|
|
140
|
-
"no-second-language-in-path": noSecondLanguageInPath,
|
|
141
|
-
|
|
165
|
+
export const rules = {
|
|
166
|
+
"prefer-arrow-export": preferArrowExport,
|
|
167
|
+
"handler-on-prefix": handlerOnPrefix,
|
|
168
|
+
"no-second-language-in-path": noSecondLanguageInPath,
|
|
169
|
+
"no-direct-const-alias": noDirectConstAlias,
|
|
170
|
+
}
|
|
142
171
|
|
|
143
172
|
/**
|
|
144
173
|
* The level this law asks for, as the plugin's own opinion.
|
package/package.json
CHANGED