@avi2dg/checks 0.45.1 → 0.46.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 +8 -0
- package/README.md +1 -0
- package/dist/presets/stylelint.js +8 -0
- package/docs/design.md +6 -2
- package/docs/gates/checks-advisories.md +3 -2
- package/docs/gates/checks-css-size.md +53 -0
- package/docs/gates/checks-flake.md +3 -2
- package/docs/gates/checks-frontend-syntax.md +5 -0
- package/docs/gates/checks-lint.md +2 -1
- package/docs/gates/checks-release-pr.md +2 -1
- package/package.json +3 -1
- package/src/core/gates.ts +3 -0
- package/src/core/main.ts +14 -0
- package/src/quality/css-size.ts +69 -0
- package/src/quality/no-plain-js.ts +3 -10
- package/src/quality/presets/stylelint.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.46.0
|
|
6
|
+
|
|
7
|
+
Released 2026-10-19.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **quality:** refuse CSS comments, non-ASCII selectors and stylesheets over 400 lines [#172](https://github.com/avi2d/checks/pull/172)
|
|
12
|
+
|
|
5
13
|
## 0.45.1
|
|
6
14
|
|
|
7
15
|
Released 2026-10-19.
|
package/README.md
CHANGED
|
@@ -172,6 +172,7 @@ The table groups the gates by vector, the part of a repository each one judges.
|
|
|
172
172
|
| quality | [`checks-comment-gate`](docs/gates/checks-comment-gate.md) | the range | every repository |
|
|
173
173
|
| quality | [`checks-effect-scope`](docs/gates/checks-effect-scope.md) | the working tree | a repository tracking `oxlint.config.ts` |
|
|
174
174
|
| quality | [`checks-frontend-syntax`](docs/gates/checks-frontend-syntax.md) | the working tree | a repository tracking `frontend-syntax.json` |
|
|
175
|
+
| quality | [`checks-css-size`](docs/gates/checks-css-size.md) | the working tree | a repository tracking `*.css` |
|
|
175
176
|
| quality | [`checks-no-plain-js`](docs/gates/checks-no-plain-js.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
176
177
|
| testing | [`checks-test-layout`](docs/gates/checks-test-layout.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
177
178
|
| testing | [`checks-quarantine-clock`](docs/gates/checks-quarantine-clock.md) | the range | every repository |
|
|
@@ -13,6 +13,12 @@ var SELECTOR_COMPLEXITY = {
|
|
|
13
13
|
"selector-max-specificity": "0,3,2",
|
|
14
14
|
"selector-max-compound-selectors": 3
|
|
15
15
|
};
|
|
16
|
+
var COMMENT_REFUSAL = {
|
|
17
|
+
"comment-word-disallowed-list": ["/^[\\s\\S]*$/"]
|
|
18
|
+
};
|
|
19
|
+
var LANGUAGE_FREE_SELECTORS = {
|
|
20
|
+
"selector-disallowed-list": ["/[^\\x00-\\x7F]/"]
|
|
21
|
+
};
|
|
16
22
|
var COLOUR_FUNCTIONS = ["rgb", "rgba", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch", "color"];
|
|
17
23
|
var DESIGN_TOKENS = {
|
|
18
24
|
"color-no-hex": true,
|
|
@@ -61,6 +67,8 @@ function defineConfig({ baseline, tokens, rules, overrides = [], ...rest }) {
|
|
|
61
67
|
const merged = {
|
|
62
68
|
...CONVENTION_CHANGES,
|
|
63
69
|
...SELECTOR_COMPLEXITY,
|
|
70
|
+
...COMMENT_REFUSAL,
|
|
71
|
+
...LANGUAGE_FREE_SELECTORS,
|
|
64
72
|
[BASELINE_RULE]: [true, { available }],
|
|
65
73
|
...tokenRules.rules,
|
|
66
74
|
...rules
|
package/docs/design.md
CHANGED
|
@@ -145,14 +145,18 @@ The violation it catches is a deleted workflow step, so it has to fail in the lo
|
|
|
145
145
|
It parses workflows with `Bun.YAML`, which the `bun` shebang already provides, so it adds no dependency.
|
|
146
146
|
It reads `on` as a string key, not as the YAML 1.1 boolean.
|
|
147
147
|
|
|
148
|
-
##
|
|
148
|
+
## TypeScript size and repetition only fail on growth
|
|
149
149
|
|
|
150
|
-
The size limits are oxlint's own rules at `error`, not a script of the kit's.
|
|
150
|
+
The TypeScript size limits are oxlint's own rules at `error`, not a script of the kit's.
|
|
151
151
|
A script of the kit's own would restate how oxlint reads its config and compares sites, and each restatement adds cases oxlint itself does not have.
|
|
152
152
|
A repository records its existing violations with `oxlint --suppress-all`, and `checks-suppressions-ratchet` refuses any count that rises.
|
|
153
153
|
The cost is that a file or function already over its limit can grow without adding a site the count sees.
|
|
154
154
|
Each repository works its counts down to zero.
|
|
155
155
|
|
|
156
|
+
`checks-css-size` holds each stylesheet to the same 400 lines as an absolute budget, with no count to ratchet.
|
|
157
|
+
oxlint does not read CSS, and no maintained stylelint rule or plugin limits lines per file, so the budget is a kit script.
|
|
158
|
+
A stylesheet already over 400 lines fails until it is split, whatever the suppression counts hold.
|
|
159
|
+
|
|
156
160
|
`checks-repetition` runs jscpd at both ends of the range with the head's `.jscpd.json`, so the same `path` and `ignore` globs pick the files at both ends.
|
|
157
161
|
It compares each file's count of repeated lines, and does not use jscpd's `--baseline-from-ref`.
|
|
158
162
|
That flag reports a repeated block as new once its text changes, so a change that shortens an existing repeated block would fail.
|
|
@@ -101,12 +101,13 @@ A range that leaves `bun.lock` unchanged runs in under a second, and one that ch
|
|
|
101
101
|
|
|
102
102
|
## Running it on a schedule
|
|
103
103
|
|
|
104
|
-
A range never fails on an advisory published after its package landed, so a scheduled `--all` run finds those
|
|
104
|
+
A range never fails on an advisory published after its package landed, so a scheduled `--all` run finds those.
|
|
105
|
+
A repository schedules it at minute 17 of its tier's 30 minute night window, so repositories do not start together:
|
|
105
106
|
|
|
106
107
|
```yaml
|
|
107
108
|
on:
|
|
108
109
|
schedule:
|
|
109
|
-
- cron: "
|
|
110
|
+
- cron: "17 22 * * *"
|
|
110
111
|
workflow_dispatch:
|
|
111
112
|
jobs:
|
|
112
113
|
advisories:
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: reference
|
|
3
|
+
audience: consumers
|
|
4
|
+
---
|
|
5
|
+
# checks-css-size
|
|
6
|
+
|
|
7
|
+
`checks-css-size` is the gate that holds each tracked stylesheet to 400 lines, the kit's own `max-lines` for code files.
|
|
8
|
+
|
|
9
|
+
## What it checks
|
|
10
|
+
|
|
11
|
+
It fails on every `*.css` file in the working tree that holds more than 400 lines.
|
|
12
|
+
It counts every line, blank lines and comments included, the way `max-lines` counts code with blank lines and comments kept.
|
|
13
|
+
Each line names the file and its line count, and tells its writer to split the stylesheet into smaller files.
|
|
14
|
+
|
|
15
|
+
## What it reads
|
|
16
|
+
|
|
17
|
+
It reads the working tree.
|
|
18
|
+
It scans the tracked and untracked files that `git ls-files --cached --others --exclude-standard` reports.
|
|
19
|
+
Ignore rules leave out untracked files only, so an untracked `node_modules/` is out of reach while a tracked stylesheet under an ignored directory is still measured.
|
|
20
|
+
A local run agrees with CI before `git add`.
|
|
21
|
+
|
|
22
|
+
## Arguments
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
checks-css-size [<directory>]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It checks the directory it runs in, or the directory it is given.
|
|
29
|
+
|
|
30
|
+
## Exit codes
|
|
31
|
+
|
|
32
|
+
| Code | When |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| 0 | every stylesheet holds 400 lines or fewer |
|
|
35
|
+
| 1 | a stylesheet holds more than 400 lines |
|
|
36
|
+
| 2 | git does not list the working tree, or a listed stylesheet cannot be read |
|
|
37
|
+
|
|
38
|
+
## Sample output
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
css-size: 1 violation(s)
|
|
42
|
+
src/styles.css: 1901 lines, over the 400-line budget; split the stylesheet into smaller files
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## When it runs
|
|
46
|
+
|
|
47
|
+
`checks-lint` runs it when the repository tracks a `*.css` file.
|
|
48
|
+
|
|
49
|
+
## Related topics
|
|
50
|
+
|
|
51
|
+
- [checks-frontend-syntax](checks-frontend-syntax.md)
|
|
52
|
+
- [checks-lint](checks-lint.md)
|
|
53
|
+
- [checks-repetition](checks-repetition.md)
|
|
@@ -101,12 +101,13 @@ Only a schedule the repository writes runs it.
|
|
|
101
101
|
|
|
102
102
|
## Running it on a schedule
|
|
103
103
|
|
|
104
|
-
A repository runs it on a schedule and keeps the record of a failed run as an artifact
|
|
104
|
+
A repository runs it on a schedule and keeps the record of a failed run as an artifact.
|
|
105
|
+
A repository schedules it at minute 27 of its tier's 30 minute night window, so repositories do not start together:
|
|
105
106
|
|
|
106
107
|
```yaml
|
|
107
108
|
on:
|
|
108
109
|
schedule:
|
|
109
|
-
- cron: "
|
|
110
|
+
- cron: "27 22 * * *"
|
|
110
111
|
workflow_dispatch:
|
|
111
112
|
jobs:
|
|
112
113
|
flake:
|
|
@@ -39,6 +39,8 @@ A control that needs selection off sets it on its own selector, such as `.drag-h
|
|
|
39
39
|
| G3 selector complexity | `selector-max-specificity` at `0,3,2` and `selector-max-compound-selectors` at 3 | on |
|
|
40
40
|
| G4 design tokens | `color-no-hex`, `color-named: "never"`, `function-disallowed-list` over `rgb`, `rgba`, `hsl`, `hsla`, `hwb`, `lab`, `lch`, `oklab`, `oklch` and `color`, and `font-size` only as one `var(--…)` with no fallback, or `inherit` | off until `tokens` names the token files, and off in those files |
|
|
41
41
|
| G5 Baseline target | `plugin/use-baseline` from `stylelint-plugin-use-baseline`, which judges each property, value, function, at-rule, selector and unit against the Baseline data, and passes a property inside an `@supports` block. It passes `@property` at every target, since a browser without `@property` treats the declaration as an unregistered custom property | on, at the target `baseline` sets |
|
|
42
|
+
| G6 comments refused | `comment-word-disallowed-list` over `/^[\s\S]*$/`, which refuses every comment, including an empty `/* */` | on |
|
|
43
|
+
| G7 language-free selectors | `selector-disallowed-list` over `/[^\x00-\x7F]/`, which refuses any selector holding a non-ASCII character, in attribute values, class names and ids alike | on |
|
|
42
44
|
|
|
43
45
|
The builder changes three of the shared config's rules:
|
|
44
46
|
|
|
@@ -47,7 +49,9 @@ The builder changes three of the shared config's rules:
|
|
|
47
49
|
- `property-no-vendor-prefix` keeps reporting without a fix, and passes `-webkit-user-select`, since its fix deletes the prefix Safari still needs.
|
|
48
50
|
- In an `.html` or `.astro` file, `declaration-block-single-line-max-declarations` is off, since a `style` attribute is one line.
|
|
49
51
|
|
|
52
|
+
The comment rule refuses every comment but a sourcemap `/*# */` one, which stylelint passes to keep the map.
|
|
50
53
|
CSS repetition is not a family of this gate: a repository adds `"css"` to `format` in `.jscpd.json`, and [checks-repetition](checks-repetition.md) then fails when a stylesheet's repeated lines grow.
|
|
54
|
+
A stylesheet's length is not a family of this gate either, and [checks-css-size](checks-css-size.md) fails one that holds more than 400 lines.
|
|
51
55
|
|
|
52
56
|
### What no comment turns off
|
|
53
57
|
|
|
@@ -169,6 +173,7 @@ A repository without that file never runs it and needs neither `stylelint.config
|
|
|
169
173
|
- [checks-browser](checks-browser.md)
|
|
170
174
|
- [checks-lint](checks-lint.md)
|
|
171
175
|
- [checks-comment-gate](checks-comment-gate.md)
|
|
176
|
+
- [checks-css-size](checks-css-size.md)
|
|
172
177
|
- [checks-suppressions-ratchet](checks-suppressions-ratchet.md)
|
|
173
178
|
- [checks-repetition](checks-repetition.md)
|
|
174
179
|
- [Native settings](../configs/native-settings.md)
|
|
@@ -14,6 +14,7 @@ Gates requiring tracked TypeScript files begin running when the repository track
|
|
|
14
14
|
`checks-advisories` begins running when the repository tracks `bun.lock`.
|
|
15
15
|
`checks-frontend-syntax` begins running when the repository tracks `frontend-syntax.json` at its root.
|
|
16
16
|
`checks-copy` begins running when the repository tracks `copy.json` at its root.
|
|
17
|
+
`checks-css-size` begins running when the repository tracks a `*.css` file.
|
|
17
18
|
All other gates run for every repository.
|
|
18
19
|
|
|
19
20
|
## What it reads
|
|
@@ -48,7 +49,7 @@ With two arguments, the base and head override range discovery.
|
|
|
48
49
|
|
|
49
50
|
```
|
|
50
51
|
checks-lint: range 2504acf098d120e73a8ece3c96f22b934f35c6a8..10ba7d8935b73ed72624120a1542e51bd21ca7c7 from HEAD against origin/main
|
|
51
|
-
checks-lint: 1 of
|
|
52
|
+
checks-lint: 1 of 19 gate(s) failed: checks-comment-gate
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
<!-- end generated lint-sample -->
|
|
@@ -80,13 +80,14 @@ Run the workflow by hand with `gh workflow run daily-release` to refresh the rel
|
|
|
80
80
|
## Running it in CI
|
|
81
81
|
|
|
82
82
|
A repository takes the daily release as `.github/workflows/daily-release.yml`.
|
|
83
|
+
A repository schedules it at minute 7 of its tier's 30 minute night window, so repositories do not start together.
|
|
83
84
|
The `pull-request` job runs on the schedule, and the `tag` job runs when a release commit lands on `main`, as [checks-release-tag](checks-release-tag.md) says:
|
|
84
85
|
|
|
85
86
|
```yaml
|
|
86
87
|
name: daily-release
|
|
87
88
|
on:
|
|
88
89
|
schedule:
|
|
89
|
-
- cron: "
|
|
90
|
+
- cron: "7 22 * * *"
|
|
90
91
|
workflow_dispatch:
|
|
91
92
|
push:
|
|
92
93
|
branches: [main]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avi2dg/checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Deterministic checks shared across a set of TypeScript repositories",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"src/docs/prose-matchers.ts",
|
|
64
64
|
"src/quality/comments.ts",
|
|
65
65
|
"src/quality/comment-gate.ts",
|
|
66
|
+
"src/quality/css-size.ts",
|
|
66
67
|
"src/quality/frontend-syntax.ts",
|
|
67
68
|
"src/quality/browser/",
|
|
68
69
|
"src/complexity/suppressions-ratchet.ts",
|
|
@@ -157,6 +158,7 @@
|
|
|
157
158
|
"checks-subsumed-tests": "src/testing/subsumed-tests.ts",
|
|
158
159
|
"checks-ci-wiring": "src/delivery/ci-wiring.ts",
|
|
159
160
|
"checks-comment-gate": "src/quality/comment-gate.ts",
|
|
161
|
+
"checks-css-size": "src/quality/css-size.ts",
|
|
160
162
|
"checks-suppressions-ratchet": "src/complexity/suppressions-ratchet.ts",
|
|
161
163
|
"checks-repetition": "src/complexity/repetition.ts",
|
|
162
164
|
"checks-unused": "src/complexity/unused.ts",
|
package/src/core/gates.ts
CHANGED
|
@@ -40,6 +40,8 @@ const OXLINT_CONFIG: TrackedContent = { pathspecs: ["oxlint.config.ts"], content
|
|
|
40
40
|
|
|
41
41
|
const FRONTEND_SYNTAX_DECLARATION: TrackedContent = { pathspecs: ["frontend-syntax.json"], content: "a frontend syntax declaration" };
|
|
42
42
|
|
|
43
|
+
const STYLESHEET: TrackedContent = { pathspecs: ["*.css"], content: "a stylesheet" };
|
|
44
|
+
|
|
43
45
|
const COPY_DECLARATION: TrackedContent = { pathspecs: ["copy.json"], content: "a copy declaration" };
|
|
44
46
|
|
|
45
47
|
export const KIT_GATES = [
|
|
@@ -49,6 +51,7 @@ export const KIT_GATES = [
|
|
|
49
51
|
{ bin: "checks-comment-gate", vector: "quality", file: "comment-gate.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
50
52
|
{ bin: "checks-effect-scope", vector: "quality", file: "effect-scope.ts", reads: "tree", args: ["--check"], appliesTo: OXLINT_CONFIG },
|
|
51
53
|
{ bin: "checks-frontend-syntax", vector: "quality", file: "frontend-syntax.ts", reads: "tree", appliesTo: FRONTEND_SYNTAX_DECLARATION },
|
|
54
|
+
{ bin: "checks-css-size", vector: "quality", file: "css-size.ts", reads: "tree", appliesTo: STYLESHEET },
|
|
52
55
|
{ bin: "checks-no-plain-js", vector: "quality", file: "no-plain-js.ts", reads: "tree", appliesTo: TYPESCRIPT_SOURCE },
|
|
53
56
|
{ bin: "checks-suppressions-ratchet", vector: "complexity", file: "suppressions-ratchet.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
54
57
|
{ bin: "checks-ci-wiring", vector: "delivery", file: "ci-wiring.ts", reads: "tree", appliesTo: EVERY_REPOSITORY },
|
package/src/core/main.ts
CHANGED
|
@@ -26,3 +26,17 @@ export function runMain<E extends { readonly message: string }>(
|
|
|
26
26
|
);
|
|
27
27
|
BunRuntime.runMain(program, { teardown: (exit, onExit) => onExit(statusOf(exit)) });
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export function runTreeGate<Outcome extends { readonly violations: readonly unknown[] }>(
|
|
31
|
+
name: string,
|
|
32
|
+
check: (root: string) => Effect.Effect<Outcome, { readonly message: string }, BunServices.BunServices>,
|
|
33
|
+
format: (result: Outcome) => string,
|
|
34
|
+
): void {
|
|
35
|
+
const gate = Effect.gen(function* () {
|
|
36
|
+
const root = process.argv[2] ?? process.cwd();
|
|
37
|
+
const result = yield* check(root);
|
|
38
|
+
yield* Console.log(format(result));
|
|
39
|
+
return result.violations.length === 0;
|
|
40
|
+
});
|
|
41
|
+
runMain(name, gate);
|
|
42
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
3
|
+
import { workingTreeFiles } from "../core/git.ts";
|
|
4
|
+
import { runTreeGate } from "../core/main.ts";
|
|
5
|
+
import { SOURCE_LIMITS } from "./presets/oxlint.ts";
|
|
6
|
+
|
|
7
|
+
export type Violation = {
|
|
8
|
+
readonly file: string;
|
|
9
|
+
readonly line: number | undefined;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const NAME = "css-size";
|
|
14
|
+
|
|
15
|
+
const LIMIT = SOURCE_LIMITS["max-lines"];
|
|
16
|
+
|
|
17
|
+
const STYLESHEET = /\.css$/;
|
|
18
|
+
|
|
19
|
+
export function lineCount(source: string): number {
|
|
20
|
+
if (source === "") return 0;
|
|
21
|
+
const parts = source.split("\n");
|
|
22
|
+
return source.endsWith("\n") ? parts.length - 1 : parts.length;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function violationsOf(entries: readonly { readonly file: string; readonly lines: number }[]): readonly Violation[] {
|
|
26
|
+
return entries
|
|
27
|
+
.filter(({ lines }) => lines > LIMIT)
|
|
28
|
+
.toSorted((a, b) => (a.file < b.file ? -1 : 1))
|
|
29
|
+
.map(({ file, lines }) => ({
|
|
30
|
+
file,
|
|
31
|
+
line: undefined,
|
|
32
|
+
message: `${lines} lines, over the ${LIMIT}-line budget; split the stylesheet into smaller files`,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class CssSizeError extends Schema.TaggedError<CssSizeError>()("CssSizeError", {
|
|
37
|
+
message: Schema.String,
|
|
38
|
+
}) {}
|
|
39
|
+
|
|
40
|
+
export type Result = { readonly files: number; readonly violations: readonly Violation[] };
|
|
41
|
+
|
|
42
|
+
const listJudged = Effect.fn("listJudged")(function* (root: string) {
|
|
43
|
+
const listed = yield* workingTreeFiles(root).pipe(
|
|
44
|
+
Effect.mapError((cause) => new CssSizeError({ message: cause.message })),
|
|
45
|
+
);
|
|
46
|
+
return listed.filter((file) => STYLESHEET.test(file)).toSorted();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const measure = Effect.fn("measure")(function* (root: string, file: string) {
|
|
50
|
+
const fs = yield* FileSystem.FileSystem;
|
|
51
|
+
const path = yield* Path.Path;
|
|
52
|
+
const source = yield* fs.readFileString(path.join(root, file)).pipe(
|
|
53
|
+
Effect.mapError((cause) => new CssSizeError({ message: `cannot read ${file}: ${String(cause)}` })),
|
|
54
|
+
);
|
|
55
|
+
return { file, lines: lineCount(source) };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const run = Effect.fn("run")(function* (root: string) {
|
|
59
|
+
const judged = yield* listJudged(root);
|
|
60
|
+
const entries = yield* Effect.forEach(judged, (file) => measure(root, file), { concurrency: "unbounded" });
|
|
61
|
+
return { files: judged.length, violations: violationsOf(entries) } satisfies Result;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export function report({ files, violations }: Result): string {
|
|
65
|
+
if (violations.length === 0) return `${NAME}: no stylesheet over ${LIMIT} lines among ${files} CSS file(s)`;
|
|
66
|
+
return [`${NAME}: ${violations.length} violation(s)`, ...violations.map(({ file, message }) => ` ${file}: ${message}`)].join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (import.meta.main) runTreeGate(NAME, run, report);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
3
|
import { workingTreeFiles } from "../core/git.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { runTreeGate } from "../core/main.ts";
|
|
5
5
|
|
|
6
6
|
export type Violation = {
|
|
7
7
|
readonly file: string;
|
|
@@ -61,11 +61,4 @@ export function report({ files, violations }: Result): string {
|
|
|
61
61
|
return [`${NAME}: ${violations.length} violation(s)`, ...violations.map(({ file, message }) => ` ${file}: ${message}`)].join("\n");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
const root = process.argv[2] ?? process.cwd();
|
|
66
|
-
const result = yield* run(root);
|
|
67
|
-
yield* Console.log(report(result));
|
|
68
|
-
return result.violations.length === 0;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
if (import.meta.main) runMain(NAME, noPlainJs);
|
|
64
|
+
if (import.meta.main) runTreeGate(NAME, run, report);
|
|
@@ -31,6 +31,16 @@ const SELECTOR_COMPLEXITY = {
|
|
|
31
31
|
"selector-max-compound-selectors": 3,
|
|
32
32
|
} as const satisfies Rules;
|
|
33
33
|
|
|
34
|
+
// [\s\S] without anchors passes an empty /**/, so the anchors refuse every comment text there is.
|
|
35
|
+
const COMMENT_REFUSAL = {
|
|
36
|
+
"comment-word-disallowed-list": ["/^[\\s\\S]*$/"],
|
|
37
|
+
} as const satisfies Rules;
|
|
38
|
+
|
|
39
|
+
// A non-ASCII class refuses every language at once, where naming scripts would chase each one.
|
|
40
|
+
const LANGUAGE_FREE_SELECTORS = {
|
|
41
|
+
"selector-disallowed-list": ["/[^\\x00-\\x7F]/"],
|
|
42
|
+
} as const satisfies Rules;
|
|
43
|
+
|
|
34
44
|
const COLOUR_FUNCTIONS = ["rgb", "rgba", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch", "color"] as const;
|
|
35
45
|
|
|
36
46
|
const DESIGN_TOKENS = {
|
|
@@ -84,6 +94,8 @@ export function defineConfig({ baseline, tokens, rules, overrides = [], ...rest
|
|
|
84
94
|
const merged: Rules = {
|
|
85
95
|
...CONVENTION_CHANGES,
|
|
86
96
|
...SELECTOR_COMPLEXITY,
|
|
97
|
+
...COMMENT_REFUSAL,
|
|
98
|
+
...LANGUAGE_FREE_SELECTORS,
|
|
87
99
|
[BASELINE_RULE]: [true, { available }],
|
|
88
100
|
...tokenRules.rules,
|
|
89
101
|
...rules,
|