legion-audit 0.1.0 → 0.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/CHANGELOG.md +111 -0
- package/README.md +63 -2
- package/dist/advice/index.d.ts +11 -0
- package/dist/advice/index.d.ts.map +1 -0
- package/dist/advice/index.js +135 -0
- package/dist/advice/index.js.map +1 -0
- package/dist/advice/lessons.d.ts +6 -0
- package/dist/advice/lessons.d.ts.map +1 -0
- package/dist/advice/lessons.js +94 -0
- package/dist/advice/lessons.js.map +1 -0
- package/dist/advice/toolchain.d.ts +5 -0
- package/dist/advice/toolchain.d.ts.map +1 -0
- package/dist/advice/toolchain.js +235 -0
- package/dist/advice/toolchain.js.map +1 -0
- package/dist/args.d.ts +3 -2
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +7 -0
- package/dist/args.js.map +1 -1
- package/dist/audit.d.ts +2 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +42 -2
- package/dist/audit.js.map +1 -1
- package/dist/checks/coverage-report.d.ts +5 -0
- package/dist/checks/coverage-report.d.ts.map +1 -0
- package/dist/checks/coverage-report.js +75 -0
- package/dist/checks/coverage-report.js.map +1 -0
- package/dist/checks/expo.d.ts +20 -0
- package/dist/checks/expo.d.ts.map +1 -0
- package/dist/checks/expo.js +138 -0
- package/dist/checks/expo.js.map +1 -0
- package/dist/checks/forge.d.ts +7 -0
- package/dist/checks/forge.d.ts.map +1 -0
- package/dist/checks/forge.js +62 -0
- package/dist/checks/forge.js.map +1 -0
- package/dist/checks/git.d.ts +2 -2
- package/dist/checks/git.d.ts.map +1 -1
- package/dist/checks/git.js +32 -17
- package/dist/checks/git.js.map +1 -1
- package/dist/checks/rule-coverage.d.ts +7 -0
- package/dist/checks/rule-coverage.d.ts.map +1 -0
- package/dist/checks/rule-coverage.js +101 -0
- package/dist/checks/rule-coverage.js.map +1 -0
- package/dist/checks/test-coverage.d.ts +3 -2
- package/dist/checks/test-coverage.d.ts.map +1 -1
- package/dist/checks/test-coverage.js +62 -4
- package/dist/checks/test-coverage.js.map +1 -1
- package/dist/checks/toolchain.d.ts +5 -0
- package/dist/checks/toolchain.d.ts.map +1 -0
- package/dist/checks/toolchain.js +142 -0
- package/dist/checks/toolchain.js.map +1 -0
- package/dist/cli.js +30 -12
- package/dist/cli.js.map +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +6 -3
- package/dist/exec.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/report/baseline.d.ts.map +1 -1
- package/dist/report/baseline.js +8 -4
- package/dist/report/baseline.js.map +1 -1
- package/dist/report/console.d.ts +4 -0
- package/dist/report/console.d.ts.map +1 -0
- package/dist/report/console.js +84 -0
- package/dist/report/console.js.map +1 -0
- package/dist/report/markdown.d.ts.map +1 -1
- package/dist/report/markdown.js +39 -1
- package/dist/report/markdown.js.map +1 -1
- package/dist/report/score.d.ts +4 -5
- package/dist/report/score.d.ts.map +1 -1
- package/dist/report/score.js +8 -7
- package/dist/report/score.js.map +1 -1
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace.d.ts +5 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +52 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
# legion-audit
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
No changes in this release.
|
|
6
|
+
|
|
7
|
+
## 0.3.0
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- 649a264: Ten new rules, a platform split, and the React Compiler flag actually working.
|
|
12
|
+
|
|
13
|
+
Two of the rules were already law in LEGION-STANDARDS with no mechanical
|
|
14
|
+
check, which is the drift §8.1 warns about: `no-hardcoded-hex` (§7, §10 lists
|
|
15
|
+
it under automatic rejection) and `no-rn-button` (§3 mandates
|
|
16
|
+
`TouchableOpacity`, not `Button`). The rest are silent-default bugs rather
|
|
17
|
+
than style: `button-has-type` (HTML defaults a bare button to `submit`, so
|
|
18
|
+
one inside a form reloads the page), `no-target-blank-without-rel`,
|
|
19
|
+
`anchor-is-not-a-button`, `img-has-alt`, `no-dangerous-html`,
|
|
20
|
+
`no-array-index-key`, `touchable-has-accessibility-label` and
|
|
21
|
+
`controlled-text-input`.
|
|
22
|
+
|
|
23
|
+
Rules now know their platform. `WEB_ONLY_RULES` gained the five DOM rules and
|
|
24
|
+
a mirrored `NATIVE_ONLY_RULES` holds the three React Native ones, so a config
|
|
25
|
+
only runs what can fire on its platform: the DOM rules are off under
|
|
26
|
+
`reactNative`, the native rules off everywhere else, and `no-hardcoded-hex`
|
|
27
|
+
and `no-array-index-key` run in both.
|
|
28
|
+
|
|
29
|
+
New rules land at level 1 in `recommended` and 3 in `strict`. The cohort is
|
|
30
|
+
exported as `INTRODUCED_AT_WARN`, so promoting them later is one edit and the
|
|
31
|
+
preset tests fail if the policy changes silently. Adding a rule should never
|
|
32
|
+
turn an existing repo's build red for code written before it existed.
|
|
33
|
+
|
|
34
|
+
`reactCompiler: true` now works when composed with a preset. It only enabled
|
|
35
|
+
`no-manual-memo` when the rule was `undefined`, but both presets set it to
|
|
36
|
+
`0` explicitly, so the flag was inert for every documented way of using it.
|
|
37
|
+
`legion-audit` also detects the compiler now — from
|
|
38
|
+
`babel-plugin-react-compiler`, `eslint-plugin-react-compiler`, or
|
|
39
|
+
`reactCompiler` in a Next config, never from the React version, since React
|
|
40
|
+
19 does not imply the compiler — and nudges when it is on while the rule is
|
|
41
|
+
off.
|
|
42
|
+
|
|
43
|
+
`no-hardcoded-hex` is off in test files, since a rule's own fixtures have to
|
|
44
|
+
contain the thing it forbids.
|
|
45
|
+
- 6c16c00: **Breaking:** `runAudit` is now `async` and returns `Promise<AuditResult>`.
|
|
46
|
+
Reading the consumer's installed plugin needs a dynamic import, which cannot
|
|
47
|
+
be done synchronously. Callers must `await` it. The `legion-audit` CLI does
|
|
48
|
+
this internally, so only code importing `runAudit` directly is affected. The
|
|
49
|
+
version bump stays minor because pre-1.0 that is where a breaking change
|
|
50
|
+
lands; a `major` here would force 1.0.0, which is a decision this change
|
|
51
|
+
should not make on its own.
|
|
52
|
+
|
|
53
|
+
PR hygiene understands more than GitHub, and the audit reports rules your
|
|
54
|
+
config never picked up.
|
|
55
|
+
|
|
56
|
+
`prHygiene` matched `/\(#\d+\)/`, which is GitHub's squash-merge subject and
|
|
57
|
+
nothing else. A GitLab, Bitbucket or Azure DevOps repo scored 0% and got a
|
|
58
|
+
nudge telling a team that reviews everything that it reviews nothing. The
|
|
59
|
+
forge is now read from the origin remote, each one gets the pattern it
|
|
60
|
+
actually writes, and the fix command matches the host (`gh pr create`,
|
|
61
|
+
`glab mr create`, `az repos pr create`). GitLab's merge-request reference
|
|
62
|
+
lives in the commit body rather than the subject, so the log format is now
|
|
63
|
+
`%s%n%b`. An unrecognized host skips the check rather than guessing, because
|
|
64
|
+
guessing is what caused this.
|
|
65
|
+
|
|
66
|
+
New `ruleCoverage` check. A config that enumerates rules by hand stops
|
|
67
|
+
receiving them: the rules added since it was written sit at 0 and nothing
|
|
68
|
+
says so. The audit now reads the rule list from the plugin _the consumer has
|
|
69
|
+
installed_, compares it against what their oxlint config names or what
|
|
70
|
+
`eslint --print-config` resolves, and nudges with the missing rules by name.
|
|
71
|
+
Reading from the installed plugin rather than a list baked into the audit
|
|
72
|
+
keeps `legion-audit` dependency-free and means it reports against the version
|
|
73
|
+
in front of it.
|
|
74
|
+
|
|
75
|
+
## 0.2.0
|
|
76
|
+
|
|
77
|
+
### Minor Changes
|
|
78
|
+
|
|
79
|
+
- 4d0ccf6: React Native support and an advice layer.
|
|
80
|
+
|
|
81
|
+
`legion-rules` gains a `reactNative` preset and a `reactNative: true` flag on
|
|
82
|
+
`defineLegionConfig`. It turns off the two rules that can only describe a
|
|
83
|
+
server-rendered page, widens `no-admin-client-in-browser` to every file
|
|
84
|
+
because a device bundle has no unreachable half, and adds the `src/`-prefixed
|
|
85
|
+
`any` boundaries. `eslint-plugin-legion` exposes it as
|
|
86
|
+
`configs.reactNative`.
|
|
87
|
+
|
|
88
|
+
`legion-audit` gains three things. An `expo-doctor` gate on Expo projects
|
|
89
|
+
that keeps a real version incompatibility blocking while demoting
|
|
90
|
+
patch-level drift to a nudge. Workspace awareness, so a package inside a
|
|
91
|
+
monorepo finds the lockfile and the hoisted bins at the workspace root
|
|
92
|
+
instead of reporting them missing. And a nudge layer: every run now ends with
|
|
93
|
+
what the repo could do better, why, and the command, including migrating off
|
|
94
|
+
npm or Yarn Classic to Yarn Berry in whichever package manager is currently
|
|
95
|
+
in use. Nudges never affect the score or the exit code. `legion-audit advice`
|
|
96
|
+
prints only those.
|
|
97
|
+
|
|
98
|
+
Two scoring changes, both so the number measures what the current code
|
|
99
|
+
controls. PR hygiene no longer scores at all: it reads history on `main`,
|
|
100
|
+
cannot be fixed retroactively, and section 12 only requires a PR on a client
|
|
101
|
+
repo, so it is now a nudge. And the test penalty is the _share_ of components
|
|
102
|
+
and hooks left uncovered rather than the count, so decomposing one oversized
|
|
103
|
+
component into ten presentational children cannot make the score worse than
|
|
104
|
+
the component it replaced. `findRegressions` compares the same share for the
|
|
105
|
+
same reason.
|
|
106
|
+
|
|
107
|
+
Test coverage is now measured rather than guessed. The audit reads
|
|
108
|
+
`coverage/coverage-summary.json` when it exists, then Istanbul's
|
|
109
|
+
`coverage-final.json` (which is what Jest's default reporters write, so it is
|
|
110
|
+
the more common of the two), then falls back to what the test files actually
|
|
111
|
+
import and `describe`, and only then to matching base names. A single test
|
|
112
|
+
file covering many components now counts for all of them.
|
|
113
|
+
|
|
3
114
|
## 0.1.0
|
|
4
115
|
|
|
5
116
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -25,11 +25,36 @@ yarn legion-audit --md AUDIT.md --json audit.json
|
|
|
25
25
|
| lint bypasses | every `eslint-disable` / `oxlint-disable` directive and `NOSONAR`, counted per rule |
|
|
26
26
|
| file-wide disables | a bare `/* eslint-disable */` or `/* oxlint-disable */`; always a hard fail |
|
|
27
27
|
| PR hygiene | share of the last 80 commits whose subject carries a `(#123)` PR number |
|
|
28
|
-
|
|
|
29
|
-
|
|
|
28
|
+
| expo-doctor | `expo-doctor` on an Expo project; patch-level drift is a nudge, not a failure |
|
|
29
|
+
| test coverage | the coverage report when there is one, otherwise what the test files actually import |
|
|
30
|
+
| lockfile | exactly one lockfile, found at the workspace root when this is a monorepo package |
|
|
31
|
+
| toolchain | package manager and Yarn flavor, framework, whether the plugin is actually wired in |
|
|
30
32
|
|
|
31
33
|
A tool that is not installed reports `skipped`, never `pass`.
|
|
32
34
|
|
|
35
|
+
### Test coverage, in order of preference
|
|
36
|
+
|
|
37
|
+
The question is which components and hooks are actually exercised, and there
|
|
38
|
+
are three ways to answer it. The audit uses the best one available and says
|
|
39
|
+
which in the report:
|
|
40
|
+
|
|
41
|
+
1. **`coverage/coverage-summary.json`** if it exists. Real per-file line
|
|
42
|
+
coverage, which is a measurement rather than a guess. Run the suite once
|
|
43
|
+
with `--coverage --coverageReporters=json-summary` to get it.
|
|
44
|
+
2. **What the test files import and `describe`.** One file covering thirteen
|
|
45
|
+
components counts for all thirteen. Group your tests however reads best;
|
|
46
|
+
the audit is not a reason to split them.
|
|
47
|
+
3. **Matching base names**, only when there is nothing better to go on.
|
|
48
|
+
|
|
49
|
+
### expo-doctor
|
|
50
|
+
|
|
51
|
+
On an Expo project the doctor runs as a gate, but not every doctor failure is
|
|
52
|
+
equal. A wrong major or minor against the installed SDK breaks builds and
|
|
53
|
+
fails the gate. A newer patch existing does not break anything, and Expo
|
|
54
|
+
ships patches faster than a project can consume them, so that is reported as
|
|
55
|
+
a nudge with the exact `expo install --fix` to run. A gate that goes red for
|
|
56
|
+
something unrelated to your code is a gate people learn to ignore.
|
|
57
|
+
|
|
33
58
|
## Output
|
|
34
59
|
|
|
35
60
|
`AUDIT.md` opens with the gate table, then per-rule counts, findings with
|
|
@@ -40,6 +65,18 @@ The score starts at 100 and subtracts documented penalties (see the bottom of
|
|
|
40
65
|
any report). It is meant for comparing runs of the same repo over time, not
|
|
41
66
|
for comparing repos to each other.
|
|
42
67
|
|
|
68
|
+
Two things deliberately do not score, because a score should measure what the
|
|
69
|
+
current state of the code controls:
|
|
70
|
+
|
|
71
|
+
- **PR hygiene is a nudge, not a penalty.** It reads commit history on `main`,
|
|
72
|
+
which cannot be fixed retroactively and corrects itself as PR-tagged commits
|
|
73
|
+
land. Section 12 requires a PR on a client repo; on your own repo it is your
|
|
74
|
+
call, and the audit is not the place to argue about it.
|
|
75
|
+
- **The test penalty is a share, not a count.** Decomposing one oversized
|
|
76
|
+
component into ten presentational children is exactly what section 1 asks
|
|
77
|
+
for, and it triples the denominator. Scoring the count would mean the score
|
|
78
|
+
got worse for doing the right thing.
|
|
79
|
+
|
|
43
80
|
## In CI
|
|
44
81
|
|
|
45
82
|
```sh
|
|
@@ -52,6 +89,30 @@ file-wide disables, more files without tests, or a lower score. `--fail-on
|
|
|
52
89
|
error` fails on any failed gate or `legion/*` error regardless of baseline;
|
|
53
90
|
`--fail-on never` always exits 0.
|
|
54
91
|
|
|
92
|
+
## Nudges
|
|
93
|
+
|
|
94
|
+
Every run ends with the nudges: what this repo could do better, why it
|
|
95
|
+
matters, and the command to do it. They are advisory. They never change the
|
|
96
|
+
score and never change the exit code, because advice that can fail your build
|
|
97
|
+
is not advice.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
legion-audit: score 71/100, all gates green
|
|
101
|
+
|
|
102
|
+
TOOLCHAIN
|
|
103
|
+
This repo runs on npm. Legion repos run on Yarn Berry.
|
|
104
|
+
why Berry writes a packageManager field into package.json, so
|
|
105
|
+
Corepack pins the exact Yarn every machine and every CI runner
|
|
106
|
+
uses.
|
|
107
|
+
fix corepack enable
|
|
108
|
+
fix yarn set version berry
|
|
109
|
+
fix rm -rf node_modules package-lock.json
|
|
110
|
+
fix yarn install
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`legion-audit advice` prints only that, skipping the slow gates. The nudges
|
|
114
|
+
also land in `AUDIT.md` under `## Nudges` and in the JSON as `advice`.
|
|
115
|
+
|
|
55
116
|
## `legion-audit directives`
|
|
56
117
|
|
|
57
118
|
The fast subcommand: only the file-wide disable scan, exit 1 if any. A
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Advice, AuditResult, DirectivesResult, LintResult, PrHygiene, RuleCoverage, TestCoverage } from '../types.js';
|
|
2
|
+
export declare const topRuleAdvice: (lint: LintResult) => Advice[];
|
|
3
|
+
export declare const testAdvice: (coverage: TestCoverage) => Advice | null;
|
|
4
|
+
export declare const bypassAdvice: (directives: DirectivesResult) => Advice | null;
|
|
5
|
+
export declare const ruleCoverageAdvice: (coverage: RuleCoverage) => Advice | null;
|
|
6
|
+
export declare const prAdvice: (hygiene: PrHygiene) => Advice | null;
|
|
7
|
+
export declare const buildAdvice: (result: Omit<AuditResult, "schema" | "generatedAt" | "score" | "advice">) => Advice[];
|
|
8
|
+
export { RULE_LESSONS } from './lessons.js';
|
|
9
|
+
export { yarnMigrationSteps, toolchainAdvice, TARGET_YARN, } from './toolchain.js';
|
|
10
|
+
export type { Lesson } from './lessons.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/advice/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,YAAY,EACZ,YAAY,EACb,MAAM,aAAa,CAAC;AAiBrB,eAAO,MAAM,aAAa,GAAI,MAAM,UAAU,KAAG,MAAM,EAoBtD,CAAC;AAWF,eAAO,MAAM,UAAU,GAAI,UAAU,YAAY,KAAG,MAAM,GAAG,IAuB5D,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,YAAY,gBAAgB,KAAG,MAAM,GAAG,IAcpE,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAAI,UAAU,YAAY,KAAG,MAAM,GAAG,IAiBpE,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,SAAS,KAAG,MAAM,GAAG,IAetD,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,QAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC,KACvE,MAAM,EAmBR,CAAC;AAEF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { RULE_LESSONS } from './lessons.js';
|
|
2
|
+
import { toolchainAdvice } from './toolchain.js';
|
|
3
|
+
import { forgeFix, forgeLabel } from '../checks/forge.js';
|
|
4
|
+
const MAX_DETAIL = 6;
|
|
5
|
+
const MEMO_RULES = new Set(['legion/no-manual-memo']);
|
|
6
|
+
const manualMemoCount = (lint) => lint.rules
|
|
7
|
+
.filter((rule) => MEMO_RULES.has(rule.rule))
|
|
8
|
+
.reduce((total, rule) => total + rule.errors + rule.warnings, 0);
|
|
9
|
+
const TOP_RULES = 3;
|
|
10
|
+
const withOverflow = (lines, total) => {
|
|
11
|
+
if (total <= lines.length)
|
|
12
|
+
return lines;
|
|
13
|
+
return [...lines, `...and ${total - lines.length} more`];
|
|
14
|
+
};
|
|
15
|
+
export const topRuleAdvice = (lint) => {
|
|
16
|
+
const ranked = lint.rules
|
|
17
|
+
.filter((rule) => RULE_LESSONS[rule.rule] !== undefined)
|
|
18
|
+
.filter((rule) => rule.errors + rule.warnings > 0)
|
|
19
|
+
.slice(0, TOP_RULES);
|
|
20
|
+
return ranked.map((rule) => {
|
|
21
|
+
const lesson = RULE_LESSONS[rule.rule];
|
|
22
|
+
const hits = lint.legion
|
|
23
|
+
.concat(lint.componentCap)
|
|
24
|
+
.filter((finding) => finding.rule === rule.rule);
|
|
25
|
+
const files = [...new Set(hits.map((finding) => finding.file))];
|
|
26
|
+
return {
|
|
27
|
+
id: `rule:${rule.rule}`,
|
|
28
|
+
area: 'rules',
|
|
29
|
+
title: `${rule.rule} fires ${rule.errors + rule.warnings} time(s).`,
|
|
30
|
+
why: lesson.why,
|
|
31
|
+
fix: lesson.fix,
|
|
32
|
+
detail: withOverflow(files.slice(0, MAX_DETAIL), files.length),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const COVERAGE_WORDING = {
|
|
37
|
+
'coverage-report': 'Measured from your coverage report, so this is what the tests actually execute.',
|
|
38
|
+
references: 'Inferred from what your test files import and describe, so a batch file covering many components counts for all of them.',
|
|
39
|
+
filenames: 'Inferred from filenames only. Run the tests with coverage once and this becomes a measurement instead of a guess.',
|
|
40
|
+
};
|
|
41
|
+
export const testAdvice = (coverage) => {
|
|
42
|
+
if (coverage.missing.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
const components = coverage.missing.filter((entry) => entry.kind === 'component').length;
|
|
45
|
+
const hooks = coverage.missing.length - components;
|
|
46
|
+
const files = coverage.missing.map((entry) => entry.file);
|
|
47
|
+
const detail = [
|
|
48
|
+
COVERAGE_WORDING[coverage.source],
|
|
49
|
+
...withOverflow(files.slice(0, MAX_DETAIL), files.length),
|
|
50
|
+
];
|
|
51
|
+
return {
|
|
52
|
+
id: 'missing-tests',
|
|
53
|
+
area: 'tests',
|
|
54
|
+
title: `${components} component(s) and ${hooks} hook(s) are not covered by a test.`,
|
|
55
|
+
why: 'Section 8: a feature without tests is incomplete. The hooks are the cheap ones to start with, because a view-model hook is a plain function that takes props and returns an object.',
|
|
56
|
+
fix: [
|
|
57
|
+
'hooks: call it with props and assert the returned object',
|
|
58
|
+
'components: Testing Library with the hook mocked',
|
|
59
|
+
'one file per component is not required; group them however reads best',
|
|
60
|
+
],
|
|
61
|
+
detail,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export const bypassAdvice = (directives) => {
|
|
65
|
+
if (directives.fileWide.length === 0)
|
|
66
|
+
return null;
|
|
67
|
+
const lesson = RULE_LESSONS['legion/scoped-disables'];
|
|
68
|
+
const files = directives.fileWide.map((hit) => `${hit.file}:${hit.line}`);
|
|
69
|
+
return {
|
|
70
|
+
id: 'file-wide-disables',
|
|
71
|
+
area: 'bypasses',
|
|
72
|
+
title: `${directives.fileWide.length} file-wide lint disable(s).`,
|
|
73
|
+
why: lesson.why,
|
|
74
|
+
fix: lesson.fix,
|
|
75
|
+
detail: withOverflow(files.slice(0, MAX_DETAIL), files.length),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const PR_HYGIENE_FLOOR = 0.2;
|
|
79
|
+
export const ruleCoverageAdvice = (coverage) => {
|
|
80
|
+
if (coverage.status !== 'checked')
|
|
81
|
+
return null;
|
|
82
|
+
if (coverage.unconfigured.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
id: 'unconfigured-rules',
|
|
86
|
+
area: 'toolchain',
|
|
87
|
+
title: `${coverage.configured.length} of ${coverage.available.length} Legion rules are configured.`,
|
|
88
|
+
why: 'A config that names every rule by hand stops receiving them. The rules added since this config was written sit at 0, nothing reports that, and the repo quietly drifts from the standard it thinks it is holding. A preset picks up new rules on upgrade; an explicit list never does.',
|
|
89
|
+
fix: [
|
|
90
|
+
'oxlint: "jsPlugins": ["legion-toolkit/eslint-plugin"] plus the recommended preset',
|
|
91
|
+
'eslint: spread plugin.configs.recommended, then override only what differs',
|
|
92
|
+
],
|
|
93
|
+
detail: withOverflow(coverage.unconfigured.slice(0, MAX_DETAIL), coverage.unconfigured.length),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export const prAdvice = (hygiene) => {
|
|
97
|
+
if (hygiene.status === 'skipped')
|
|
98
|
+
return null;
|
|
99
|
+
if (hygiene.ratio >= PR_HYGIENE_FLOOR)
|
|
100
|
+
return null;
|
|
101
|
+
const direct = hygiene.inspected - hygiene.withPr;
|
|
102
|
+
return {
|
|
103
|
+
id: 'pr-hygiene',
|
|
104
|
+
area: 'process',
|
|
105
|
+
title: `${direct} of the last ${hygiene.inspected} commits landed straight on the branch (${forgeLabel(hygiene.forge)}).`,
|
|
106
|
+
why: 'Not a problem by itself, and it does not affect the score. Section 12 only requires a PR on a client repo, where nothing reaches main without review. On your own repo it is your call, so the thing to check is that the commit messages carry what a PR description would have said, because they are then the only record.',
|
|
107
|
+
fix: [
|
|
108
|
+
`client repo: ${forgeFix(hygiene.forge).join(', ')}, and never merge it yourself`,
|
|
109
|
+
'own repo: nothing to change, as long as the commit message says why',
|
|
110
|
+
],
|
|
111
|
+
detail: [],
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
export const buildAdvice = (result) => {
|
|
115
|
+
const entries = [
|
|
116
|
+
...toolchainAdvice(result.toolchain, result.lockfile, result.expo, manualMemoCount(result.lint)),
|
|
117
|
+
...topRuleAdvice(result.lint),
|
|
118
|
+
];
|
|
119
|
+
const tests = testAdvice(result.testCoverage);
|
|
120
|
+
if (tests)
|
|
121
|
+
entries.push(tests);
|
|
122
|
+
const bypasses = bypassAdvice(result.directives);
|
|
123
|
+
if (bypasses)
|
|
124
|
+
entries.push(bypasses);
|
|
125
|
+
const coverage = ruleCoverageAdvice(result.ruleCoverage);
|
|
126
|
+
if (coverage)
|
|
127
|
+
entries.push(coverage);
|
|
128
|
+
const process = prAdvice(result.prHygiene);
|
|
129
|
+
if (process)
|
|
130
|
+
entries.push(process);
|
|
131
|
+
return entries;
|
|
132
|
+
};
|
|
133
|
+
export { RULE_LESSONS } from './lessons.js';
|
|
134
|
+
export { yarnMigrationSteps, toolchainAdvice, TARGET_YARN, } from './toolchain.js';
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/advice/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lessons.d.ts","sourceRoot":"","sources":["../../src/advice/lessons.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA4F/C,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const RULE_LESSONS = {
|
|
2
|
+
'legion/no-function-keyword': {
|
|
3
|
+
why: 'A default export written as `export default function App` loses its name in React DevTools and in stack traces, so a crash report points at Anonymous. Arrow consts also hoist the way you expect instead of silently working before their definition.',
|
|
4
|
+
fix: ['const App = () => { ... };', 'export default App;'],
|
|
5
|
+
},
|
|
6
|
+
'legion/presentational-components': {
|
|
7
|
+
why: 'A component that holds state, effects or a handler body cannot be rendered in a test without also standing up everything it fetches. Moving the logic into use<Component> gives you a plain function to unit test and a component that is only markup.',
|
|
8
|
+
fix: [
|
|
9
|
+
'create lib/hooks/use<Component>.ts',
|
|
10
|
+
'move the state, effects and handlers into it',
|
|
11
|
+
'return a flat object of values and handleX functions',
|
|
12
|
+
'the component becomes: call the hook, early return, render children',
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
'legion/no-literal-conditions-in-jsx': {
|
|
16
|
+
why: 'A comparison in the markup is a business rule hidden where nobody greps for it. When the rule changes you have to find every JSX file that encoded it, and you will miss one.',
|
|
17
|
+
fix: [
|
|
18
|
+
'derive the flag in the hook: const showBanner = status === "expired";',
|
|
19
|
+
'render with the flag: {showBanner && <Banner />}',
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
'legion/no-narrative-comments': {
|
|
23
|
+
why: 'Comments drift. The code changes, the comment does not, and now the file actively lies to the next reader. Names and small functions cannot drift because they are the thing being executed.',
|
|
24
|
+
fix: [
|
|
25
|
+
'delete the comment',
|
|
26
|
+
'if it explained what, rename until the name says it',
|
|
27
|
+
'if it explained why, that belongs in the PR description',
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
'legion/no-react-fc': {
|
|
31
|
+
why: 'React.FC adds an implicit children prop to components that do not take children, and it gets in the way of generics. A plain typed props parameter says exactly what the component accepts.',
|
|
32
|
+
fix: ['const Card = ({ title }: CardProps) => { ... }'],
|
|
33
|
+
},
|
|
34
|
+
'legion/no-enum': {
|
|
35
|
+
why: 'A TypeScript enum emits a runtime object, so it survives into the bundle and is not erasable. A string-literal union is compile-time only and narrows better.',
|
|
36
|
+
fix: ["type Status = 'idle' | 'loading' | 'done';"],
|
|
37
|
+
},
|
|
38
|
+
'legion/guarded-context-hook': {
|
|
39
|
+
why: 'A raw useContext returns undefined outside its provider, so the failure surfaces later as a property access on undefined, somewhere unrelated. A guarded hook fails at the call site with the provider name in the message.',
|
|
40
|
+
fix: [
|
|
41
|
+
'export const useAppContext = () => {',
|
|
42
|
+
' const ctx = useContext(AppContext);',
|
|
43
|
+
' if (!ctx) throw new Error("useAppContext must be used within AppProvider");',
|
|
44
|
+
' return ctx;',
|
|
45
|
+
'};',
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
'legion/no-client-globals-in-state-init': {
|
|
49
|
+
why: 'The server has no localStorage, so it renders one value and the browser hydrates with another. React throws away the server markup and you get a flash of the wrong content.',
|
|
50
|
+
fix: [
|
|
51
|
+
'initialize from a prop or a URL value',
|
|
52
|
+
'read the client-only preference in a useEffect and reconcile',
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
'legion/no-use-client-in-page': {
|
|
56
|
+
why: 'A "use client" page ships the whole route to the browser and gives up server-side data fetching for it. The point of the app router is that the page fetches and only the interactive leaf is client.',
|
|
57
|
+
fix: [
|
|
58
|
+
'keep page.tsx a server component that fetches',
|
|
59
|
+
'move the interactivity into <Route>Client.tsx with the directive',
|
|
60
|
+
'pass the fetched data down as props',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
'legion/no-admin-client-in-browser': {
|
|
64
|
+
why: 'The service-role key bypasses row level security. Anything that reaches the browser or a device bundle can be read out of it, and then the key is not a secret any more.',
|
|
65
|
+
fix: [
|
|
66
|
+
'use the browser or server client here',
|
|
67
|
+
'keep admin queries behind a route handler or a server component',
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
'legion/scoped-disables': {
|
|
71
|
+
why: 'A file-wide disable turns the rule off for every line in the file, including the code nobody has written yet. A scoped disable with a reason turns it off for exactly the line that needed it and tells the next reader why.',
|
|
72
|
+
fix: ['eslint-disable-next-line <rule> -- <the reason>'],
|
|
73
|
+
},
|
|
74
|
+
'legion/no-manual-memo': {
|
|
75
|
+
why: 'Under the React Compiler the memoization is automatic, so a hand-written useMemo is a dependency array to keep correct for no gain. A wrong dep array is a real bug; no dep array cannot be.',
|
|
76
|
+
fix: ['drop the useMemo and write the value directly'],
|
|
77
|
+
},
|
|
78
|
+
'no-ternary': {
|
|
79
|
+
why: 'Nested ternaries are where render logic goes to hide. A named flag reads as the rule it encodes, and a two-way branch is usually a child component or a Record lookup.',
|
|
80
|
+
fix: [
|
|
81
|
+
'default: value ?? fallback',
|
|
82
|
+
'render branch: {isReady && <Ready />} or an early return',
|
|
83
|
+
'two-way: a child component, or a Record keyed by the value',
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
'max-lines': {
|
|
87
|
+
why: 'The cap is a symptom check, not the rule. A component over 180 lines has almost always absorbed logic that belongs in a hook, and the length is just how you noticed.',
|
|
88
|
+
fix: [
|
|
89
|
+
'move state, effects and handlers into use<Component>',
|
|
90
|
+
'extract each section of the markup into its own child component',
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=lessons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lessons.js","sourceRoot":"","sources":["../../src/advice/lessons.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,4BAA4B,EAAE;QAC5B,GAAG,EAAE,wPAAwP;QAC7P,GAAG,EAAE,CAAC,4BAA4B,EAAE,qBAAqB,CAAC;KAC3D;IACD,kCAAkC,EAAE;QAClC,GAAG,EAAE,wPAAwP;QAC7P,GAAG,EAAE;YACH,oCAAoC;YACpC,8CAA8C;YAC9C,sDAAsD;YACtD,qEAAqE;SACtE;KACF;IACD,qCAAqC,EAAE;QACrC,GAAG,EAAE,+KAA+K;QACpL,GAAG,EAAE;YACH,uEAAuE;YACvE,kDAAkD;SACnD;KACF;IACD,8BAA8B,EAAE;QAC9B,GAAG,EAAE,8LAA8L;QACnM,GAAG,EAAE;YACH,oBAAoB;YACpB,qDAAqD;YACrD,yDAAyD;SAC1D;KACF;IACD,oBAAoB,EAAE;QACpB,GAAG,EAAE,6LAA6L;QAClM,GAAG,EAAE,CAAC,gDAAgD,CAAC;KACxD;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,+JAA+J;QACpK,GAAG,EAAE,CAAC,4CAA4C,CAAC;KACpD;IACD,6BAA6B,EAAE;QAC7B,GAAG,EAAE,6NAA6N;QAClO,GAAG,EAAE;YACH,sCAAsC;YACtC,uCAAuC;YACvC,+EAA+E;YAC/E,eAAe;YACf,IAAI;SACL;KACF;IACD,wCAAwC,EAAE;QACxC,GAAG,EAAE,8KAA8K;QACnL,GAAG,EAAE;YACH,uCAAuC;YACvC,8DAA8D;SAC/D;KACF;IACD,8BAA8B,EAAE;QAC9B,GAAG,EAAE,uMAAuM;QAC5M,GAAG,EAAE;YACH,+CAA+C;YAC/C,kEAAkE;YAClE,qCAAqC;SACtC;KACF;IACD,mCAAmC,EAAE;QACnC,GAAG,EAAE,0KAA0K;QAC/K,GAAG,EAAE;YACH,uCAAuC;YACvC,iEAAiE;SAClE;KACF;IACD,wBAAwB,EAAE;QACxB,GAAG,EAAE,8NAA8N;QACnO,GAAG,EAAE,CAAC,iDAAiD,CAAC;KACzD;IACD,uBAAuB,EAAE;QACvB,GAAG,EAAE,8LAA8L;QACnM,GAAG,EAAE,CAAC,+CAA+C,CAAC;KACvD;IACD,YAAY,EAAE;QACZ,GAAG,EAAE,wKAAwK;QAC7K,GAAG,EAAE;YACH,4BAA4B;YAC5B,0DAA0D;YAC1D,4DAA4D;SAC7D;KACF;IACD,WAAW,EAAE;QACX,GAAG,EAAE,uKAAuK;QAC5K,GAAG,EAAE;YACH,sDAAsD;YACtD,iEAAiE;SAClE;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Advice, ExpoDoctorResult, LockfileResult, Toolchain } from '../types.js';
|
|
2
|
+
export declare const TARGET_YARN = "yarn@4.18.0";
|
|
3
|
+
export declare const yarnMigrationSteps: (found: string[]) => string[];
|
|
4
|
+
export declare const toolchainAdvice: (toolchain: Toolchain, lockfile: LockfileResult, expo: ExpoDoctorResult, memoFindings?: number) => Advice[];
|
|
5
|
+
//# sourceMappingURL=toolchain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolchain.d.ts","sourceRoot":"","sources":["../../src/advice/toolchain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,SAAS,EACV,MAAM,aAAa,CAAC;AAGrB,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,EAQ1D,CAAC;AAuNF,eAAO,MAAM,eAAe,GAC1B,WAAW,SAAS,EACpB,UAAU,cAAc,EACxB,MAAM,gBAAgB,EACtB,qBAAgB,KACf,MAAM,EAgBR,CAAC"}
|