@starci/eslint-canon-fe 1.4.1 → 1.4.2
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/contract.mjs +1363 -1295
- package/contracts.ts +1 -1
- package/file-layout.mjs +414 -310
- package/icon.mjs +10 -96
- package/landmark.mjs +242 -242
- package/lint-adoption.mjs +116 -116
- package/naming.mjs +36 -7
- package/package.json +38 -38
- package/props-and-slots.mjs +278 -14
- package/props.ts +33 -33
- package/served-locale.mjs +1 -1
- package/the-split.mjs +2 -2
- package/typography.mjs +83 -83
- package/vendor-boundary.mjs +85 -62
package/lint-adoption.mjs
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Repository-level audit for effective StarCi FE lint adoption.
|
|
3
|
-
*
|
|
4
|
-
* An ESLint config can import a plugin, copy a plugin, or call itself StarCi while still leaving
|
|
5
|
-
* canonical rules switched off. The only useful evidence is the effective config ESLint applies
|
|
6
|
-
* to a real production file. This audit compares that printed config with this canon.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Where the component tree sits, per supported repository shape.
|
|
10
|
-
*
|
|
11
|
-
* ONE BRANCH, IN ONE PLACE. The difference between a monorepo and a single app is not a difference
|
|
12
|
-
* of law - the rules, their severities and the inline-config refusal are identical. It is only
|
|
13
|
-
* WHERE the law applies. Written out in each repository's config, that fact became two hand-kept
|
|
14
|
-
* lists, and the lists did what hand-kept lists do: one enabled 43 rules, the other 53, and canon
|
|
15
|
-
* had 49. Neither repository was wrong on purpose; nothing told either of them.
|
|
16
|
-
*
|
|
17
|
-
* The candidate path is included deliberately. A Preview candidate is the exact source Apply later
|
|
18
|
-
* ports into production, and leaving it ungoverned means the one file that becomes production was
|
|
19
|
-
* judged by nothing.
|
|
20
|
-
*/
|
|
21
|
-
const LAYOUT_GLOBS = {
|
|
22
|
-
monorepo: [
|
|
23
|
-
"packages/ui/src/**/*.{ts,tsx}",
|
|
24
|
-
"apps/*/src/**/*.{ts,tsx}",
|
|
25
|
-
"**/candidate/src/**/*.{ts,tsx}",
|
|
26
|
-
],
|
|
27
|
-
"single-app": [
|
|
28
|
-
"src/**/*.{ts,tsx}",
|
|
29
|
-
"**/candidate/src/**/*.{ts,tsx}",
|
|
30
|
-
],
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** The repository shapes this canon knows how to govern. */
|
|
34
|
-
export const LAYOUTS = Object.keys(LAYOUT_GLOBS)
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The one flat-config block that attaches this canon to a repository.
|
|
38
|
-
*
|
|
39
|
-
* A consuming `eslint.config.mjs` spreads the result and owns nothing else about the rules: not
|
|
40
|
-
* which are on, not their severity, not whether an inline comment may switch one off. Its own
|
|
41
|
-
* plugins, its own ignores and its own language options stay its own business - what it may not
|
|
42
|
-
* keep is a second opinion about the law.
|
|
43
|
-
*
|
|
44
|
-
* @param {object} input - Attachment options.
|
|
45
|
-
* @param {"monorepo" | "single-app"} input.layout - Which shape this repository has.
|
|
46
|
-
* @param {object} input.plugin - The plugin object, imported from this canon.
|
|
47
|
-
* @param {object} input.recommended - The recommendation, imported from this canon.
|
|
48
|
-
* @param {object} input.linterOptions - The linter options, imported from this canon.
|
|
49
|
-
* @returns {object} One flat-config block.
|
|
50
|
-
*/
|
|
51
|
-
export const starciFeConfig = ({ layout, plugin, recommended, linterOptions }) => {
|
|
52
|
-
const files = LAYOUT_GLOBS[layout]
|
|
53
|
-
if (!files) {
|
|
54
|
-
throw new Error(`unknown layout "${layout}" - expected one of: ${LAYOUTS.join(", ")}`)
|
|
55
|
-
}
|
|
56
|
-
/*
|
|
57
|
-
* `recommended` is a FLAT rule map here, not a config object with a `rules` key - and reaching
|
|
58
|
-
* for `.rules` on it is the mistake this line exists to stop repeating. Spread that way it
|
|
59
|
-
* yields `undefined`, the block ends up with zero rules, and the repository lints clean while
|
|
60
|
-
* being governed by nothing. That is the worst possible failure for this file: green, silent,
|
|
61
|
-
* and indistinguishable from success. Both spellings are accepted so neither shape can produce
|
|
62
|
-
* an empty block.
|
|
63
|
-
*/
|
|
64
|
-
const rules = recommended?.rules ?? recommended
|
|
65
|
-
if (!rules || Object.keys(rules).length === 0) {
|
|
66
|
-
throw new Error("starciFeConfig received an empty recommendation - a config with no rules is not adoption")
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
files,
|
|
70
|
-
linterOptions,
|
|
71
|
-
plugins: { "starci-fe": plugin },
|
|
72
|
-
rules: { ...rules },
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Convert every flat-config severity spelling to its numeric meaning. */
|
|
77
|
-
const severityOf = (setting) => {
|
|
78
|
-
const severity = Array.isArray(setting) ? setting[0] : setting
|
|
79
|
-
if (severity === "error") return 2
|
|
80
|
-
if (severity === "warn") return 1
|
|
81
|
-
if (severity === "off") return 0
|
|
82
|
-
return severity
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Compare an `eslint --print-config` result with the canonical recommendation.
|
|
87
|
-
*
|
|
88
|
-
* @param {object} printedConfig - Parsed effective ESLint config for one production probe file.
|
|
89
|
-
* @param {Record<string, string | number | unknown[]>} expectedRules - Canonical rule levels.
|
|
90
|
-
*/
|
|
91
|
-
export const auditLintAdoption = (printedConfig, expectedRules) => {
|
|
92
|
-
const actualRules = printedConfig?.rules ?? {}
|
|
93
|
-
const missing = []
|
|
94
|
-
const nonError = []
|
|
95
|
-
|
|
96
|
-
for (const name of Object.keys(expectedRules).sort()) {
|
|
97
|
-
if (actualRules[name] === undefined) {
|
|
98
|
-
missing.push(name)
|
|
99
|
-
continue
|
|
100
|
-
}
|
|
101
|
-
if (severityOf(actualRules[name]) !== 2) nonError.push(name)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const refusesInlineConfig = printedConfig?.linterOptions?.noInlineConfig === true
|
|
105
|
-
return {
|
|
106
|
-
ok: missing.length === 0 && nonError.length === 0 && refusesInlineConfig,
|
|
107
|
-
missing,
|
|
108
|
-
nonError,
|
|
109
|
-
refusesInlineConfig,
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Repository audits are gathered beside rules, but are not ESLint AST rules themselves. */
|
|
114
|
-
export const audits = { "effective-config": auditLintAdoption }
|
|
115
|
-
export const rules = {}
|
|
116
|
-
export const recommended = {}
|
|
1
|
+
/**
|
|
2
|
+
* Repository-level audit for effective StarCi FE lint adoption.
|
|
3
|
+
*
|
|
4
|
+
* An ESLint config can import a plugin, copy a plugin, or call itself StarCi while still leaving
|
|
5
|
+
* canonical rules switched off. The only useful evidence is the effective config ESLint applies
|
|
6
|
+
* to a real production file. This audit compares that printed config with this canon.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Where the component tree sits, per supported repository shape.
|
|
10
|
+
*
|
|
11
|
+
* ONE BRANCH, IN ONE PLACE. The difference between a monorepo and a single app is not a difference
|
|
12
|
+
* of law - the rules, their severities and the inline-config refusal are identical. It is only
|
|
13
|
+
* WHERE the law applies. Written out in each repository's config, that fact became two hand-kept
|
|
14
|
+
* lists, and the lists did what hand-kept lists do: one enabled 43 rules, the other 53, and canon
|
|
15
|
+
* had 49. Neither repository was wrong on purpose; nothing told either of them.
|
|
16
|
+
*
|
|
17
|
+
* The candidate path is included deliberately. A Preview candidate is the exact source Apply later
|
|
18
|
+
* ports into production, and leaving it ungoverned means the one file that becomes production was
|
|
19
|
+
* judged by nothing.
|
|
20
|
+
*/
|
|
21
|
+
const LAYOUT_GLOBS = {
|
|
22
|
+
monorepo: [
|
|
23
|
+
"packages/ui/src/**/*.{ts,tsx}",
|
|
24
|
+
"apps/*/src/**/*.{ts,tsx}",
|
|
25
|
+
"**/candidate/src/**/*.{ts,tsx}",
|
|
26
|
+
],
|
|
27
|
+
"single-app": [
|
|
28
|
+
"src/**/*.{ts,tsx}",
|
|
29
|
+
"**/candidate/src/**/*.{ts,tsx}",
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The repository shapes this canon knows how to govern. */
|
|
34
|
+
export const LAYOUTS = Object.keys(LAYOUT_GLOBS)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The one flat-config block that attaches this canon to a repository.
|
|
38
|
+
*
|
|
39
|
+
* A consuming `eslint.config.mjs` spreads the result and owns nothing else about the rules: not
|
|
40
|
+
* which are on, not their severity, not whether an inline comment may switch one off. Its own
|
|
41
|
+
* plugins, its own ignores and its own language options stay its own business - what it may not
|
|
42
|
+
* keep is a second opinion about the law.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} input - Attachment options.
|
|
45
|
+
* @param {"monorepo" | "single-app"} input.layout - Which shape this repository has.
|
|
46
|
+
* @param {object} input.plugin - The plugin object, imported from this canon.
|
|
47
|
+
* @param {object} input.recommended - The recommendation, imported from this canon.
|
|
48
|
+
* @param {object} input.linterOptions - The linter options, imported from this canon.
|
|
49
|
+
* @returns {object} One flat-config block.
|
|
50
|
+
*/
|
|
51
|
+
export const starciFeConfig = ({ layout, plugin, recommended, linterOptions }) => {
|
|
52
|
+
const files = LAYOUT_GLOBS[layout]
|
|
53
|
+
if (!files) {
|
|
54
|
+
throw new Error(`unknown layout "${layout}" - expected one of: ${LAYOUTS.join(", ")}`)
|
|
55
|
+
}
|
|
56
|
+
/*
|
|
57
|
+
* `recommended` is a FLAT rule map here, not a config object with a `rules` key - and reaching
|
|
58
|
+
* for `.rules` on it is the mistake this line exists to stop repeating. Spread that way it
|
|
59
|
+
* yields `undefined`, the block ends up with zero rules, and the repository lints clean while
|
|
60
|
+
* being governed by nothing. That is the worst possible failure for this file: green, silent,
|
|
61
|
+
* and indistinguishable from success. Both spellings are accepted so neither shape can produce
|
|
62
|
+
* an empty block.
|
|
63
|
+
*/
|
|
64
|
+
const rules = recommended?.rules ?? recommended
|
|
65
|
+
if (!rules || Object.keys(rules).length === 0) {
|
|
66
|
+
throw new Error("starciFeConfig received an empty recommendation - a config with no rules is not adoption")
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
files,
|
|
70
|
+
linterOptions,
|
|
71
|
+
plugins: { "starci-fe": plugin },
|
|
72
|
+
rules: { ...rules },
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Convert every flat-config severity spelling to its numeric meaning. */
|
|
77
|
+
const severityOf = (setting) => {
|
|
78
|
+
const severity = Array.isArray(setting) ? setting[0] : setting
|
|
79
|
+
if (severity === "error") return 2
|
|
80
|
+
if (severity === "warn") return 1
|
|
81
|
+
if (severity === "off") return 0
|
|
82
|
+
return severity
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Compare an `eslint --print-config` result with the canonical recommendation.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} printedConfig - Parsed effective ESLint config for one production probe file.
|
|
89
|
+
* @param {Record<string, string | number | unknown[]>} expectedRules - Canonical rule levels.
|
|
90
|
+
*/
|
|
91
|
+
export const auditLintAdoption = (printedConfig, expectedRules) => {
|
|
92
|
+
const actualRules = printedConfig?.rules ?? {}
|
|
93
|
+
const missing = []
|
|
94
|
+
const nonError = []
|
|
95
|
+
|
|
96
|
+
for (const name of Object.keys(expectedRules).sort()) {
|
|
97
|
+
if (actualRules[name] === undefined) {
|
|
98
|
+
missing.push(name)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (severityOf(actualRules[name]) !== 2) nonError.push(name)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const refusesInlineConfig = printedConfig?.linterOptions?.noInlineConfig === true
|
|
105
|
+
return {
|
|
106
|
+
ok: missing.length === 0 && nonError.length === 0 && refusesInlineConfig,
|
|
107
|
+
missing,
|
|
108
|
+
nonError,
|
|
109
|
+
refusesInlineConfig,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Repository audits are gathered beside rules, but are not ESLint AST rules themselves. */
|
|
114
|
+
export const audits = { "effective-config": auditLintAdoption }
|
|
115
|
+
export const rules = {}
|
|
116
|
+
export const recommended = {}
|
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
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@starci/eslint-canon-fe",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "The StarCi front-end lint canon: every rule its laws publish, gathered as one flat-config plugin.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "UNLICENSED",
|
|
7
|
-
"private": false,
|
|
8
|
-
"exports": {
|
|
9
|
-
".": "./index.mjs",
|
|
10
|
-
"./contracts": "./contracts.ts",
|
|
11
|
-
"./props": "./props.ts"
|
|
12
|
-
},
|
|
13
|
-
"main": "./index.mjs",
|
|
14
|
-
"files": [
|
|
15
|
-
"*.mjs",
|
|
16
|
-
"*.ts",
|
|
17
|
-
"!*.test.mjs",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=20.9.0"
|
|
22
|
-
},
|
|
23
|
-
"peerDependencies": {
|
|
24
|
-
"eslint": ">=9"
|
|
25
|
-
},
|
|
26
|
-
"publishConfig": {
|
|
27
|
-
"access": "public"
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"test": "node --test \"*.test.mjs\""
|
|
31
|
-
},
|
|
32
|
-
"keywords": [
|
|
33
|
-
"eslint",
|
|
34
|
-
"eslintplugin",
|
|
35
|
-
"starci",
|
|
36
|
-
"canon"
|
|
37
|
-
]
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@starci/eslint-canon-fe",
|
|
3
|
+
"version": "1.4.2",
|
|
4
|
+
"description": "The StarCi front-end lint canon: every rule its laws publish, gathered as one flat-config plugin.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"private": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.mjs",
|
|
10
|
+
"./contracts": "./contracts.ts",
|
|
11
|
+
"./props": "./props.ts"
|
|
12
|
+
},
|
|
13
|
+
"main": "./index.mjs",
|
|
14
|
+
"files": [
|
|
15
|
+
"*.mjs",
|
|
16
|
+
"*.ts",
|
|
17
|
+
"!*.test.mjs",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.9.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"eslint": ">=9"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "node --test \"*.test.mjs\""
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"eslint",
|
|
34
|
+
"eslintplugin",
|
|
35
|
+
"starci",
|
|
36
|
+
"canon"
|
|
37
|
+
]
|
|
38
|
+
}
|