@shivaedev/quality 0.12.0 → 0.14.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 +16 -0
- package/README.md +9 -3
- package/biome/plugins/effect-fn-functions.grit +15 -0
- package/biome/plugins/effect-test-bodies.grit +12 -0
- package/biome/preset.json +8 -0
- package/dist/imports/fences/evaluate.js +2 -2
- package/dist/imports/fences/evaluate.js.map +1 -1
- package/dist/naming/testName.d.ts +1 -0
- package/dist/naming/testName.d.ts.map +1 -1
- package/dist/naming/testName.js +4 -0
- package/dist/naming/testName.js.map +1 -1
- package/dist/rules/max-lines.js +4 -4
- package/dist/rules/max-lines.js.map +1 -1
- package/package.json +1 -1
- package/src/imports/fences/evaluate.ts +2 -2
- package/src/naming/testName.ts +6 -0
- package/src/rules/max-lines.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 - 2026-10-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- The `effect-fn-functions` plugin reports a top-level function whose whole body is `Effect.gen(…)`, written as an arrow or as a function that only returns it. Write it as `const loadItem = Effect.fn("ItemStore.loadItem")(function* (id: string) { … })`, which names its span after the function, or as `Effect.fnUntraced(…)` when it needs no span. A function nested in another, an object member, a `.pipe(…)` on the `Effect.gen` and a function that runs other statements before it are not reported. The rule has no fix.
|
|
8
|
+
- The `effect-test-bodies` plugin reports, in test code, a callback whose whole body is `Effect.gen(function* () { … })` passed to `it`, `test` or one of their members, such as `it.effect` or `it.effect.skipIf(…)`. Pass the generator to the test instead, `it.effect("loads the item", function* () { … })`, and let the test helper run it with `Effect.gen`; `@shivaedev/effect-test/it.ts` provides such an `it`. Test code is a test file or a file under `test-support/`, as for every other rule. The rule has no fix.
|
|
9
|
+
- A repository that upgrades records the existing findings of the two plugins with `quality baseline write --rule biome/plugin`.
|
|
10
|
+
|
|
11
|
+
## 0.13.0 - 2026-10-05
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Test code has one definition: a test file, as the `tests/*` rules read the name, or any file under a `test-support/` folder. `test-support/` is the one folder name for test helpers, and every rule that checks shipped code skips it. `tests/follow` and `tests/colocated` still read only test files.
|
|
16
|
+
- `imports/fences` skips files under `test-support/` too, beside test and spec files, so a harness may import what the tests need.
|
|
17
|
+
- `structure/max-lines` gives test code the test limit without configuration. `testFiles` now adds further folders, such as a Playwright suite in `e2e/`, and is empty by default: `*.test.*` and `*.spec.*` count without it, and a helper in a `test/`, `tests/` or `__tests__/` folder is source until it moves to `test-support/` or the option names its folder. A config that listed the old defaults keeps working.
|
|
18
|
+
|
|
3
19
|
## 0.12.0 - 2026-10-05
|
|
4
20
|
|
|
5
21
|
### Changed
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ The config is typed: an unknown rule id, a misspelled option or an option of the
|
|
|
56
56
|
|
|
57
57
|
## Rules
|
|
58
58
|
|
|
59
|
-
`structure/max-lines` keeps each module to one job: a source file may have 150 lines and
|
|
59
|
+
`structure/max-lines` keeps each module to one job: a source file may have 150 lines and [test code](#test-code) 300, counted the way an editor numbers them. Declaration files are exempt. Its options are `source` and `test` (the limits) and `testFiles`, `.gitignore` patterns of further folders whose files count as tests, such as a Playwright suite in `e2e/` (none by default). A file over its limit counts one violation for each line above it: 168 lines under a limit of 150 count 18.
|
|
60
60
|
|
|
61
61
|
### Comments
|
|
62
62
|
|
|
@@ -217,7 +217,7 @@ Each import resolves the way the compiler resolves it, with the options of the n
|
|
|
217
217
|
- A module that resolves into `node_modules` or outside the root is external, and its package is the one it resolves into, whatever alias the import uses. A `@types` package counts as the package it describes: `@types/hast` is `hast`, `@types/scope__name` is `@scope/name`.
|
|
218
218
|
|
|
219
219
|
```ts
|
|
220
|
-
"imports/resolvable": { options: { generated: ["packages/db/test/generated"] } },
|
|
220
|
+
"imports/resolvable": { options: { generated: ["packages/db/src/test-support/generated"] } },
|
|
221
221
|
```
|
|
222
222
|
|
|
223
223
|
`imports/cycles` reports each group of modules that import each other at run time once, at the alphabetically first of them, with one loop through the group. Its count is the number of modules in the group. `import type`, `export type`, type references and imports in declaration files are left out; `import { type X }` stays a runtime import. Dynamic `import()` and `require()` count; `require.resolve()` does not. It takes no registry exceptions. Each finding of `imports/resolvable` has the import as its subject.
|
|
@@ -274,7 +274,7 @@ Targets are `packages(...)` (workspace packages by name, with or without their s
|
|
|
274
274
|
|
|
275
275
|
The config does not compile without `demonstratedBy`, and the rule checks the examples against the policy: each is a chain of imports from a file of the repository, which may end in `external(name)`. The illegal example must cross this fence and no other; the legal example must cross none. Every name a fence uses must exist: a package, a folder that holds checked files, a checked file, a subject of the unit. Two fences may not share a name, and each needs a reason. A policy that breaks any of this stops the run. Fences count type imports too.
|
|
276
276
|
|
|
277
|
-
Fences guard shipped code, and
|
|
277
|
+
Fences guard shipped code, and [test code](#test-code) ships nowhere, so a fence never holds it: a test file or a file under `test-support/` may import across every fence. There is no option to check them.
|
|
278
278
|
|
|
279
279
|
A finding has its fence's name as its subject, so a registry entry with that subject excuses one file from one fence.
|
|
280
280
|
|
|
@@ -422,6 +422,8 @@ A name says what a thing is, and the same kind of thing is named the same way ev
|
|
|
422
422
|
| Schemas | PascalCase and named like their type: `const Item = Schema.Struct(...)` with `type Item = typeof Item.Type`. | `schema-names` plugin |
|
|
423
423
|
| `Schema.Struct` fields | camelCase, also when quoted. Outside data keeps its keys only at the edge: `Schema.Struct({ createdAt: Schema.String }).pipe(Schema.encodeKeys({ createdAt: "created_at" }))`. | `schema-struct-keys` plugin, `key-names` plugin |
|
|
424
424
|
| `Effect.fn` spans | `"Owner.operation"`, where the owner is the service or module and the operation is the name the function is bound to: `const loadItem = Effect.fn("ItemStore.loadItem")`, and `loadItem: Effect.fn("ItemStore.loadItem")` in an object. | `effect-fn-spans` plugin |
|
|
425
|
+
| `Effect.gen` functions | A top-level function whose whole body is `Effect.gen` is an `Effect.fn`: `const loadItem = Effect.fn("ItemStore.loadItem")(function* (id: string) { … })`, or `Effect.fnUntraced` when it needs no span. | `effect-fn-functions` plugin |
|
|
426
|
+
| Effect test bodies | In test code, a test takes the generator itself: `it.effect("loads the item", function* () { … })`, never `() => Effect.gen(function* () { … })`. The test helper runs it with `Effect.gen`. | `effect-test-bodies` plugin |
|
|
425
427
|
| Service Layers | A static `layer` on the service, read as `ItemStore.layer`. No exported `ItemStoreLive` or `ItemStoreLayer` constant. A Layer that wires an application together stays unexported. | `service-layers` plugin |
|
|
426
428
|
| Private members | A `#field`. No `private`, `protected` or `public` modifier. | `useConsistentMemberAccessibility` |
|
|
427
429
|
| React | A function passed to an `onX` prop is `handleX`, an `onX` prop forwarded as it is, or a state setter passed as it is: `onOpenChange={setOpen}`. A context is `ThemeContext`, a ref `inputRef`, an id `fieldId`. A module that exports a component exports only components; a test may define the components it renders. | `handler-names` plugin, `useReactNamingConvention`, `useComponentExportOnlyModules` |
|
|
@@ -511,6 +513,10 @@ A test sits beside the code it covers, and its name says what it covers and wher
|
|
|
511
513
|
|
|
512
514
|
Both rules take a `suites` option, `.gitignore` patterns of folders that hold tests with their own layout, such as tests across several packages or a Playwright suite, whose `.spec.ts` files mean something else. The rules skip those folders.
|
|
513
515
|
|
|
516
|
+
### Test code
|
|
517
|
+
|
|
518
|
+
Test code is a test file, as `tests/follow` reads the name (`*.test.ts` or `*.spec.ts`, with any environment), or any file under a folder named `test-support/`. `test-support/` is the one folder name for the fixtures, harnesses and generated clients that tests share. Test code ships nowhere, so the rules that check shipped code skip it: `imports/fences` never holds it, and `structure/max-lines` gives it the test limit. The test rules still read only test files: a file under `test-support/` is not a test, so `tests/follow` and `tests/colocated` leave it alone.
|
|
519
|
+
|
|
514
520
|
## Vitest projects
|
|
515
521
|
|
|
516
522
|
`@shivaedev/quality/vitest.ts` sets up the tests of a package by file name, so no test file sets its environment with a pragma. It needs `vitest`, and `happy-dom` for DOM tests.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
engine biome(1.0)
|
|
2
|
+
language js(typescript, jsx)
|
|
3
|
+
|
|
4
|
+
or {
|
|
5
|
+
JsVariableDeclarator(id=$name, initializer=JsInitializerClause(expression=JsArrowFunctionExpression(body=$body))),
|
|
6
|
+
JsFunctionDeclaration(id=$name, body=$body),
|
|
7
|
+
JsFunctionExportDefaultDeclaration(id=$name, body=$body)
|
|
8
|
+
} as $function where {
|
|
9
|
+
$body <: or {
|
|
10
|
+
JsFunctionBody(statements=[JsReturnStatement(argument=`Effect.gen($...)`)]),
|
|
11
|
+
`Effect.gen($...)`
|
|
12
|
+
},
|
|
13
|
+
! $function <: within JsFunctionBody() ,
|
|
14
|
+
register_diagnostic(span=$name, message="A top-level function whose whole body is Effect.gen is an Effect.fn, which names its span and keeps the call site in traces: const loadItem = Effect.fn('ItemStore.loadItem')(function* (id: string) { ... }). Effect.fnUntraced fits a function that needs no span.")
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
engine biome(1.0)
|
|
2
|
+
language js(typescript, jsx)
|
|
3
|
+
|
|
4
|
+
`$test($title, $callback, $...)` where {
|
|
5
|
+
$test <: r"it|test|it\.[A-Za-z.]+|test\.[A-Za-z.]+|it\.[A-Za-z.]+\(.*\)|test\.[A-Za-z.]+\(.*\)",
|
|
6
|
+
$callback <: JsArrowFunctionExpression(body=$body),
|
|
7
|
+
$body <: or {
|
|
8
|
+
`Effect.gen(function* () { $... })`,
|
|
9
|
+
JsFunctionBody(statements=[JsReturnStatement(argument=`Effect.gen(function* () { $... })`)])
|
|
10
|
+
},
|
|
11
|
+
register_diagnostic(span=$callback, message="A test body that is only Effect.gen is the generator itself: pass the function* to the test, as in it.effect('loads the item', function* () { ... }), and the test helper runs it with Effect.gen. @shivaedev/effect-test/it.ts provides an it whose effect and live testers take one.")
|
|
12
|
+
}
|
package/biome/preset.json
CHANGED
|
@@ -647,6 +647,14 @@
|
|
|
647
647
|
"includes": ["**/*.ts", "**/*.tsx", "!**/*.d.ts"],
|
|
648
648
|
"path": "./node_modules/@shivaedev/quality/biome/plugins/effect-fn-spans.grit"
|
|
649
649
|
},
|
|
650
|
+
{
|
|
651
|
+
"includes": ["**/*.ts", "**/*.tsx", "!**/*.d.ts"],
|
|
652
|
+
"path": "./node_modules/@shivaedev/quality/biome/plugins/effect-fn-functions.grit"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
"includes": ["**/*.test.*", "**/*.spec.*", "**/test-support/**", "!**/*.d.ts"],
|
|
656
|
+
"path": "./node_modules/@shivaedev/quality/biome/plugins/effect-test-bodies.grit"
|
|
657
|
+
},
|
|
650
658
|
{
|
|
651
659
|
"includes": ["**/*.ts", "**/*.tsx", "!**/*.d.ts"],
|
|
652
660
|
"path": "./node_modules/@shivaedev/quality/biome/plugins/constant-names.grit"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isTestCode } from "#naming/testName.ts";
|
|
2
2
|
import { compile } from "./match.js";
|
|
3
3
|
import { labelOf, outgoing, reachedFrom } from "./reach.js";
|
|
4
4
|
import { compileVocabulary } from "./vocabulary.js";
|
|
@@ -44,7 +44,7 @@ export function compileFence(compiler, fence) {
|
|
|
44
44
|
const where = `fence "${fence.name}"`;
|
|
45
45
|
const from = compile(compiler, fence.from, `${where}.from`);
|
|
46
46
|
function importer(path) {
|
|
47
|
-
return
|
|
47
|
+
return !isTestCode(path) && from({ kind: "file", path });
|
|
48
48
|
}
|
|
49
49
|
const prohibition = fence.prohibition;
|
|
50
50
|
if (prohibition.kind === "vocabulary") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../../src/imports/fences/evaluate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../../src/imports/fences/evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testName.d.ts","sourceRoot":"","sources":["../../src/naming/testName.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,uCAAwC,CAAC;AAElE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAQ3D"}
|
|
1
|
+
{"version":3,"file":"testName.d.ts","sourceRoot":"","sources":["../../src/naming/testName.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,uCAAwC,CAAC;AAElE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAQ3D;AAID,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD"}
|
package/dist/naming/testName.js
CHANGED
|
@@ -10,4 +10,8 @@ export function testName(path) {
|
|
|
10
10
|
const [stem = "", ...modifiers] = groups.base.split(".");
|
|
11
11
|
return { extension: groups.extension, folder: posix.dirname(path), kind, modifiers, stem };
|
|
12
12
|
}
|
|
13
|
+
const TEST_SUPPORT = "test-support";
|
|
14
|
+
export function isTestCode(path) {
|
|
15
|
+
return testName(path) !== undefined || posix.dirname(path).split("/").includes(TEST_SUPPORT);
|
|
16
|
+
}
|
|
13
17
|
//# sourceMappingURL=testName.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testName.js","sourceRoot":"","sources":["../../src/naming/testName.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAU,CAAC;AAYlE,MAAM,SAAS,GAAG,gEAAgE,CAAC;AAEnF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC;IAC1B,IAAI,MAAM,EAAE,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;QAC1G,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzD,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC5F,CAAC"}
|
|
1
|
+
{"version":3,"file":"testName.js","sourceRoot":"","sources":["../../src/naming/testName.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAU,CAAC;AAYlE,MAAM,SAAS,GAAG,gEAAgE,CAAC;AAEnF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC;IAC1B,IAAI,MAAM,EAAE,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;QAC1G,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzD,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC5F,CAAC;AAED,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAC9F,CAAC"}
|
package/dist/rules/max-lines.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import ignore from "ignore";
|
|
3
|
+
import { isTestCode } from "#naming/testName.ts";
|
|
3
4
|
import { defineRule } from "#rule.ts";
|
|
4
|
-
const DEFAULT_TEST_FILES = ["*.test.*", "*.spec.*", "test/", "tests/", "__tests__/"];
|
|
5
5
|
const DECLARATION = /\.d\.[cm]?ts$/u;
|
|
6
6
|
const Limit = Schema.Int.check(Schema.isGreaterThan(0));
|
|
7
7
|
const MaxLinesOptions = Schema.Struct({
|
|
8
8
|
source: Limit.pipe(Schema.withDecodingDefaultKey(Effect.succeed(150))),
|
|
9
9
|
test: Limit.pipe(Schema.withDecodingDefaultKey(Effect.succeed(300))),
|
|
10
|
-
testFiles: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(
|
|
10
|
+
testFiles: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
11
11
|
});
|
|
12
12
|
const oversized = (file, limit) => file.lines.length <= limit
|
|
13
13
|
? []
|
|
@@ -21,10 +21,10 @@ const oversized = (file, limit) => file.lines.length <= limit
|
|
|
21
21
|
];
|
|
22
22
|
export const maxLines = defineRule({
|
|
23
23
|
check: ({ options, sources }) => {
|
|
24
|
-
const
|
|
24
|
+
const suites = ignore().add([...options.testFiles]);
|
|
25
25
|
return sources
|
|
26
26
|
.filter((file) => !DECLARATION.test(file.path))
|
|
27
|
-
.flatMap((file) => oversized(file,
|
|
27
|
+
.flatMap((file) => oversized(file, isTestCode(file.path) || suites.ignores(file.path) ? options.test : options.source));
|
|
28
28
|
},
|
|
29
29
|
description: "Keep each module to one job. Split a long file along its responsibilities; never golf it under the limit.",
|
|
30
30
|
id: "structure/max-lines",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"max-lines.js","sourceRoot":"","sources":["../../src/rules/max-lines.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"max-lines.js","sourceRoot":"","sources":["../../src/rules/max-lines.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAiC,MAAM,UAAU,CAAC;AAErE,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;CAC9F,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,CAAC,IAAgB,EAAE,KAAa,EAAsB,EAAE,CACzE,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK;IACzB,CAAC,CAAC,EAAE;IACJ,CAAC,CAAC;QACA;YACC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK;YAChC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,sBAAsB,KAAK,cAAc;YACtE,SAAS,EAAE,KAAK;SAChB;KACD,CAAC;AAEL,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IAClC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE;QAC/B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACpD,OAAO,OAAO;aACZ,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC9C,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1H,CAAC;IACD,WAAW,EAAE,2GAA2G;IACxH,EAAE,EAAE,qBAAqB;IACzB,OAAO,EAAE,MAAM,CAAC,kBAAkB,CAAC,eAAe,EAAE,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,CAAC;CACnH,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/quality",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Repository quality gate with typed rules, a baseline of existing debt and a registry of reasoned exceptions",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/quality#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ImportEdge, ImportGraph } from "#imports/graph.ts";
|
|
2
|
-
import {
|
|
2
|
+
import { isTestCode } from "#naming/testName.ts";
|
|
3
3
|
import type { Finding } from "#rule.ts";
|
|
4
4
|
import { type Compiler, compile, type Matcher } from "./match.ts";
|
|
5
5
|
import type { Fence } from "./model.ts";
|
|
@@ -65,7 +65,7 @@ export function compileFence(compiler: Compiler, fence: Fence): CompiledFence {
|
|
|
65
65
|
const where = `fence "${fence.name}"`;
|
|
66
66
|
const from = compile(compiler, fence.from, `${where}.from`);
|
|
67
67
|
function importer(path: string): boolean {
|
|
68
|
-
return
|
|
68
|
+
return !isTestCode(path) && from({ kind: "file", path });
|
|
69
69
|
}
|
|
70
70
|
const prohibition = fence.prohibition;
|
|
71
71
|
if (prohibition.kind === "vocabulary") {
|
package/src/naming/testName.ts
CHANGED
|
@@ -23,3 +23,9 @@ export function testName(path: string): TestName | undefined {
|
|
|
23
23
|
const [stem = "", ...modifiers] = groups.base.split(".");
|
|
24
24
|
return { extension: groups.extension, folder: posix.dirname(path), kind, modifiers, stem };
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
const TEST_SUPPORT = "test-support";
|
|
28
|
+
|
|
29
|
+
export function isTestCode(path: string): boolean {
|
|
30
|
+
return testName(path) !== undefined || posix.dirname(path).split("/").includes(TEST_SUPPORT);
|
|
31
|
+
}
|
package/src/rules/max-lines.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import ignore from "ignore";
|
|
3
|
+
import { isTestCode } from "#naming/testName.ts";
|
|
3
4
|
import { defineRule, type Finding, type SourceFile } from "#rule.ts";
|
|
4
5
|
|
|
5
|
-
const DEFAULT_TEST_FILES: readonly string[] = ["*.test.*", "*.spec.*", "test/", "tests/", "__tests__/"];
|
|
6
6
|
const DECLARATION = /\.d\.[cm]?ts$/u;
|
|
7
7
|
|
|
8
8
|
const Limit = Schema.Int.check(Schema.isGreaterThan(0));
|
|
@@ -10,7 +10,7 @@ const Limit = Schema.Int.check(Schema.isGreaterThan(0));
|
|
|
10
10
|
const MaxLinesOptions = Schema.Struct({
|
|
11
11
|
source: Limit.pipe(Schema.withDecodingDefaultKey(Effect.succeed(150))),
|
|
12
12
|
test: Limit.pipe(Schema.withDecodingDefaultKey(Effect.succeed(300))),
|
|
13
|
-
testFiles: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(
|
|
13
|
+
testFiles: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
const oversized = (file: SourceFile, limit: number): readonly Finding[] =>
|
|
@@ -27,10 +27,10 @@ const oversized = (file: SourceFile, limit: number): readonly Finding[] =>
|
|
|
27
27
|
|
|
28
28
|
export const maxLines = defineRule({
|
|
29
29
|
check: ({ options, sources }) => {
|
|
30
|
-
const
|
|
30
|
+
const suites = ignore().add([...options.testFiles]);
|
|
31
31
|
return sources
|
|
32
32
|
.filter((file) => !DECLARATION.test(file.path))
|
|
33
|
-
.flatMap((file) => oversized(file,
|
|
33
|
+
.flatMap((file) => oversized(file, isTestCode(file.path) || suites.ignores(file.path) ? options.test : options.source));
|
|
34
34
|
},
|
|
35
35
|
description: "Keep each module to one job. Split a long file along its responsibilities; never golf it under the limit.",
|
|
36
36
|
id: "structure/max-lines",
|