@brighterly/lib-fe-codestyle 0.3.0 → 0.4.1
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 +13 -6
- package/dist/invariants/rules/index.d.ts +2 -2
- package/dist/invariants/rules/index.js +2 -2
- package/dist/invariants/rules/regression.js +7 -2
- package/dist/invariants/run.js +4 -1
- package/dist/invariants/types.d.ts +4 -0
- package/dist/invariants/util.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,10 @@ cs-app, app-tutor, app-admin.
|
|
|
5
5
|
|
|
6
6
|
It holds the **mechanisms**. Domain rules stay in each app.
|
|
7
7
|
|
|
8
|
+
**New here?** [docs/how-it-works.md](docs/how-it-works.md) — how a check reads real
|
|
9
|
+
code, what the gate can and cannot cover, what is already enforced, how to extend it,
|
|
10
|
+
and how to run it.
|
|
11
|
+
|
|
8
12
|
## Subpaths
|
|
9
13
|
|
|
10
14
|
| Import | What it is |
|
|
@@ -53,9 +57,9 @@ exported as data.
|
|
|
53
57
|
|
|
54
58
|
| Rule | Level | Checks |
|
|
55
59
|
|---|---|---|
|
|
56
|
-
| `core/R1` |
|
|
60
|
+
| `core/R1` | warn* | bare `localStorage` / `sessionStorage` — the accessor itself throws on Safari ITP and in-app webviews |
|
|
57
61
|
| `core/R2` | error | SCSS rules inline in a Vue `<style>` block instead of a referenced `.scss` file |
|
|
58
|
-
| `core/R3` |
|
|
62
|
+
| `core/R3` | warn* | `JSON.parse` with no enclosing `try`/`catch` — a corrupt entry breaks that path permanently |
|
|
59
63
|
| `core/R4` | error | `@font-face` declaring a font *stack* — an invalid descriptor, so the browser drops the face silently |
|
|
60
64
|
| `core/C1` | warn | hand-rolled browser API with a VueUse equivalent |
|
|
61
65
|
| `core/C2` | warn | a component duplicating an existing UI-kit component name |
|
|
@@ -65,10 +69,12 @@ exported as data.
|
|
|
65
69
|
| `core/C6` | warn | a raw `font-family` / `font-size` / `font-weight` declaration instead of the app's typography source |
|
|
66
70
|
| `core/C7` | warn | `<script>` attribute order differing from the app's canonical (`setup lang` vs `lang setup`) |
|
|
67
71
|
|
|
68
|
-
Level discipline: `error` is
|
|
69
|
-
|
|
70
|
-
(
|
|
71
|
-
|
|
72
|
+
Level discipline: `error` is for crash-class rules (`core/R*`); `warn` is for
|
|
73
|
+
conventions (`core/C*`). `core/R1`/`core/R3` (marked *) ship at `warn` **as an interim
|
|
74
|
+
state** (2026-09-25): their fleet backlog is real code nobody has bandwidth to migrate
|
|
75
|
+
and re-verify right now. Their target level is `error` — promote once the backlog
|
|
76
|
+
clears (a breaking-style bump). `core/R2`/`core/R4` stay `error`. A consumer's `levels`
|
|
77
|
+
ceiling is still never the tool for a backlog on an `error` rule — fix or allowlist.
|
|
72
78
|
Underscore-prefixed SCSS partials (`_extends.scss`, `_colors-*.scss`, …) are declaration
|
|
73
79
|
files by convention in every Brighterly app and are exempt from `core/C3`/`core/C6` wholesale.
|
|
74
80
|
|
|
@@ -108,6 +114,7 @@ export const settings = {
|
|
|
108
114
|
typesRoot: 'app/types', // core/C5 (default shown)
|
|
109
115
|
typographySource: 'the font() mixin from @scss/_mixins.scss', // core/C6 — cs-app points this at its AppText component instead
|
|
110
116
|
scriptAttrOrder: 'setup-first', // core/C7 — or 'lang-first' (default 'setup-first')
|
|
117
|
+
selfTestFile: 'invariants/self-test.mts', // its ignore-strings are fixture DATA — skipped by IG0/IG1
|
|
111
118
|
}
|
|
112
119
|
```
|
|
113
120
|
|
|
@@ -28,11 +28,11 @@ export declare const commonRules: Rule[];
|
|
|
28
28
|
* instead. A test pins this map to `commonRules`, so it cannot drift.
|
|
29
29
|
*/
|
|
30
30
|
export declare const sharedRuleIds: {
|
|
31
|
-
/** `
|
|
31
|
+
/** `warn` (target `error`, interim 2026-09-25) — bare `localStorage`/`sessionStorage` access. The accessor itself throws SecurityError on Safari ITP, Firefox strict mode and in-app webviews; `if (localStorage)` guards throw too. Fix: the app's safe storage wrapper (`settings.safeStorageModule`). */
|
|
32
32
|
readonly 'core/R1': "core/R1";
|
|
33
33
|
/** `error` — SCSS rules written inline in a Vue `<style>` block. Inline styles drift from the design tokens invisibly. Fix: a dedicated `.scss` file referenced via `<style scoped lang="scss" src="…" />`. */
|
|
34
34
|
readonly 'core/R2': "core/R2";
|
|
35
|
-
/** `
|
|
35
|
+
/** `warn` (target `error`, interim 2026-09-25) — `JSON.parse` with no enclosing `try`/`catch`. A corrupt stored value crashes that code path permanently for the visitor. Fix: the app's guarded parse helper (`settings.safeJsonParse`) or try/catch. */
|
|
36
36
|
readonly 'core/R3': "core/R3";
|
|
37
37
|
/** `error` — `@font-face` declaring a font *stack*. Inside `@font-face`, `font-family` names the face being defined; a comma-separated stack is invalid and the browser drops the face silently. Fix: single family name inside, the stack on the selectors that use it. */
|
|
38
38
|
readonly 'core/R4': "core/R4";
|
|
@@ -28,11 +28,11 @@ export const commonRules = [...rRules, ...cRules];
|
|
|
28
28
|
* instead. A test pins this map to `commonRules`, so it cannot drift.
|
|
29
29
|
*/
|
|
30
30
|
export const sharedRuleIds = {
|
|
31
|
-
/** `
|
|
31
|
+
/** `warn` (target `error`, interim 2026-09-25) — bare `localStorage`/`sessionStorage` access. The accessor itself throws SecurityError on Safari ITP, Firefox strict mode and in-app webviews; `if (localStorage)` guards throw too. Fix: the app's safe storage wrapper (`settings.safeStorageModule`). */
|
|
32
32
|
'core/R1': 'core/R1',
|
|
33
33
|
/** `error` — SCSS rules written inline in a Vue `<style>` block. Inline styles drift from the design tokens invisibly. Fix: a dedicated `.scss` file referenced via `<style scoped lang="scss" src="…" />`. */
|
|
34
34
|
'core/R2': 'core/R2',
|
|
35
|
-
/** `
|
|
35
|
+
/** `warn` (target `error`, interim 2026-09-25) — `JSON.parse` with no enclosing `try`/`catch`. A corrupt stored value crashes that code path permanently for the visitor. Fix: the app's guarded parse helper (`settings.safeJsonParse`) or try/catch. */
|
|
36
36
|
'core/R3': 'core/R3',
|
|
37
37
|
/** `error` — `@font-face` declaring a font *stack*. Inside `@font-face`, `font-family` names the face being defined; a comma-separated stack is invalid and the browser drops the face silently. Fix: single family name inside, the stack on the selectors that use it. */
|
|
38
38
|
'core/R4': 'core/R4',
|
|
@@ -14,7 +14,11 @@ import { eachLine, setting } from './shared.js';
|
|
|
14
14
|
const bareStorage = {
|
|
15
15
|
id: 'core/R1',
|
|
16
16
|
title: 'bare localStorage / sessionStorage access',
|
|
17
|
-
level:
|
|
17
|
+
// Target level is `error` (crash-class). Shipped at `warn` since 2026-09-25: the fleet
|
|
18
|
+
// backlog (cs-app ~42 sites, main-app ~28) is real code the team has no bandwidth to
|
|
19
|
+
// migrate and re-verify right now, and a rushed storage migration is its own risk.
|
|
20
|
+
// Promote back to `error` once the backlog is cleared — that is a major-style bump.
|
|
21
|
+
level: 'warn',
|
|
18
22
|
why: 'the bare API throws SecurityError on Safari ITP, Firefox strict mode and in-app webviews (Instagram/Facebook/TikTok) — the `if (localStorage)` guard does not help, the property accessor itself throws',
|
|
19
23
|
fix: 'use the app\'s safe storage wrapper instead of the bare API',
|
|
20
24
|
scope: ['app/**/*.{ts,vue}'],
|
|
@@ -68,7 +72,8 @@ const scssInStyleBlock = {
|
|
|
68
72
|
const unguardedJsonParse = {
|
|
69
73
|
id: 'core/R3',
|
|
70
74
|
title: 'unguarded JSON.parse',
|
|
71
|
-
level
|
|
75
|
+
// Target level is `error` — see core/R1 above; same interim decision (2026-09-25).
|
|
76
|
+
level: 'warn',
|
|
72
77
|
async: true,
|
|
73
78
|
why: 'a truncated write or a hand-edited storage entry makes JSON.parse throw on every re-entry to that code path, until the visitor clears storage — the failure is permanent for them, not transient',
|
|
74
79
|
fix: 'parse through the app\'s guarded helper, or wrap the call in try/catch',
|
package/dist/invariants/run.js
CHANGED
|
@@ -228,7 +228,9 @@ export const run = async (options) => {
|
|
|
228
228
|
const ignoreCache = new Map();
|
|
229
229
|
const ignoresFor = (file) => {
|
|
230
230
|
if (!ignoreCache.has(file)) {
|
|
231
|
-
|
|
231
|
+
// The consumer's self-test file holds literal `invariant-ignore:` strings as
|
|
232
|
+
// fixture DATA — parsing them would report the fixtures themselves (IG0/IG1).
|
|
233
|
+
const src = file === settings.selfTestFile ? null : read(file);
|
|
232
234
|
ignoreCache.set(file, src === null ? { suppressions: new Map(), unreasoned: [], comments: [] } : parseIgnores(src));
|
|
233
235
|
}
|
|
234
236
|
return ignoreCache.get(file);
|
|
@@ -348,6 +350,7 @@ export const run = async (options) => {
|
|
|
348
350
|
live, retired, allowlists, levels, ignores,
|
|
349
351
|
configFile,
|
|
350
352
|
configSource: read(configFile) ?? undefined,
|
|
353
|
+
pathExists: p => read(p) !== null,
|
|
351
354
|
})) {
|
|
352
355
|
findings.push({ ruleId: 'IG1', level: 'error', ...v });
|
|
353
356
|
}
|
|
@@ -74,6 +74,8 @@ export interface SharedRuleSettings {
|
|
|
74
74
|
typographySource?: string;
|
|
75
75
|
/** `core/C7`: which `<script>` attribute order is canonical in this app. Default 'setup-first' (`<script setup lang="ts">`). */
|
|
76
76
|
scriptAttrOrder?: 'setup-first' | 'lang-first';
|
|
77
|
+
/** The consumer file whose `invariant-ignore:` strings are self-test FIXTURE DATA, not real suppressions — the engine skips ignore-parsing it entirely, replacing per-file IG0/IG1 allowlist boilerplate. E.g. 'invariants/self-test.mts'. */
|
|
78
|
+
selfTestFile?: string;
|
|
77
79
|
}
|
|
78
80
|
/** What a rule's `check` receives. */
|
|
79
81
|
export interface RuleContext {
|
|
@@ -164,6 +166,8 @@ export interface DanglingRefInput {
|
|
|
164
166
|
configFile?: string;
|
|
165
167
|
/** Contents of `configFile`, so a stale key can be reported on its own line. */
|
|
166
168
|
configSource?: string;
|
|
169
|
+
/** Answers "does this repo-relative file exist?" — enables the allowlist-path staleness check. */
|
|
170
|
+
pathExists?: (path: string) => boolean;
|
|
167
171
|
}
|
|
168
172
|
/** An invariant a built-in ESLint rule already covers. */
|
|
169
173
|
export interface LinterOwnedRule {
|
package/dist/invariants/util.js
CHANGED
|
@@ -214,6 +214,21 @@ export const findDanglingRuleRefs = (input) => {
|
|
|
214
214
|
continue;
|
|
215
215
|
out.push({ file: configFile, line: lineOfKey('levels', id), message: `levels names "${id}", but ${explain(id)} — the severity override does nothing; drop it` });
|
|
216
216
|
}
|
|
217
|
+
// Allowlist PATHS had no staleness guard: an entry whose file is gone (or renamed)
|
|
218
|
+
// is inert and silent. Found live in app-funnels — a dead BtnAuthGoogle.vue entry
|
|
219
|
+
// survived a fork divergence (2026-09-25). Checked only when the caller can answer
|
|
220
|
+
// existence, so the self-test can still exercise this function without a filesystem.
|
|
221
|
+
if (input.pathExists) {
|
|
222
|
+
for (const [id, entries] of Object.entries(allowlists)) {
|
|
223
|
+
if (!live.has(id))
|
|
224
|
+
continue; // the dead-id finding above already covers the whole entry
|
|
225
|
+
for (const path of Object.keys(entries ?? {})) {
|
|
226
|
+
if (input.pathExists(path))
|
|
227
|
+
continue;
|
|
228
|
+
out.push({ file: configFile, line: lineOfKey('allowlists', path), message: `allowlists["${id}"] names "${path}", but no such file exists — the exemption is inert; drop it or fix the path` });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
217
232
|
for (const { file, line, ids } of ignores) {
|
|
218
233
|
for (const id of ids) {
|
|
219
234
|
if (live.has(id))
|
package/package.json
CHANGED