@shivaedev/quality 0.31.0 → 0.31.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/CHANGELOG.md +6 -0
- package/dist/imports/fences/reach.d.ts +5 -4
- package/dist/imports/fences/reach.d.ts.map +1 -1
- package/dist/imports/fences/reach.js +3 -2
- package/dist/imports/fences/reach.js.map +1 -1
- package/dist/rules/stories/unitTests.js +2 -2
- package/dist/rules/stories/unitTests.js.map +1 -1
- package/dist/rules/test-names/usesDom.d.ts.map +1 -1
- package/dist/rules/test-names/usesDom.js +11 -4
- package/dist/rules/test-names/usesDom.js.map +1 -1
- package/package.json +1 -1
- package/src/imports/fences/reach.ts +17 -11
- package/src/rules/stories/unitTests.ts +3 -3
- package/src/rules/test-names/usesDom.ts +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.1 - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Resolve DOM globals by scope when checking unit tests and their test-support fixtures, so local variables, parameters and destructured bindings named `document` pass while global DOM access is still reported.
|
|
8
|
+
|
|
3
9
|
## 0.31.0 - 2026-10-19
|
|
4
10
|
|
|
5
11
|
### Added
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ImportEdge } from "#imports/graph.ts";
|
|
2
2
|
import type { Endpoint } from "#imports/resolve.ts";
|
|
3
3
|
import type { Matcher } from "./match.ts";
|
|
4
|
-
export interface Reached {
|
|
4
|
+
export interface Reached<TMatch = unknown> {
|
|
5
5
|
readonly line: number;
|
|
6
|
+
readonly match: TMatch;
|
|
6
7
|
readonly path: readonly string[];
|
|
7
8
|
readonly target: Endpoint;
|
|
8
9
|
}
|
|
@@ -12,10 +13,10 @@ export interface Trail {
|
|
|
12
13
|
}
|
|
13
14
|
export declare function labelOf(endpoint: Endpoint): string;
|
|
14
15
|
export declare function outgoing(edges: readonly ImportEdge[]): ReadonlyMap<string, readonly ImportEdge[]>;
|
|
15
|
-
export interface Walked {
|
|
16
|
-
readonly reached: readonly Reached[];
|
|
16
|
+
export interface Walked<TMatch = unknown> {
|
|
17
|
+
readonly reached: readonly Reached<TMatch>[];
|
|
17
18
|
readonly visited: ReadonlyMap<string, Trail>;
|
|
18
19
|
}
|
|
19
|
-
export declare function walkFrom(start: string, edgesFrom: ReadonlyMap<string, readonly ImportEdge[]>, forbidden:
|
|
20
|
+
export declare function walkFrom<TMatch>(start: string, edgesFrom: ReadonlyMap<string, readonly ImportEdge[]>, forbidden: (endpoint: Endpoint) => TMatch | undefined): Walked<TMatch>;
|
|
20
21
|
export declare function reachedFrom(start: string, edgesFrom: ReadonlyMap<string, readonly ImportEdge[]>, forbidden: Matcher): readonly Reached[];
|
|
21
22
|
//# sourceMappingURL=reach.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../../../src/imports/fences/reach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,OAAO;
|
|
1
|
+
{"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../../../src/imports/fences/reach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,OAAO,CAAC,MAAM,GAAG,OAAO;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAMlD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC,CAQjG;AAqBD,MAAM,WAAW,MAAM,CAAC,MAAM,GAAG,OAAO;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IAC7C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC7C;AAGD,wBAAgB,QAAQ,CAAC,MAAM,EAC9B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC,EACrD,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,GAAG,SAAS,GACnD,MAAM,CAAC,MAAM,CAAC,CAShB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,GAAG,SAAS,OAAO,EAAE,CAExI"}
|
|
@@ -17,8 +17,9 @@ export function outgoing(edges) {
|
|
|
17
17
|
function follow(walk, before, edge) {
|
|
18
18
|
const label = labelOf(edge.to);
|
|
19
19
|
const trail = { line: before.path.length === 1 ? edge.line : before.line, path: [...before.path, label] };
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const match = walk.forbidden(edge.to);
|
|
21
|
+
if (match) {
|
|
22
|
+
walk.reached.set(label, walk.reached.get(label) ?? { ...trail, match, target: edge.to });
|
|
22
23
|
}
|
|
23
24
|
else if (edge.to.kind === "file" && !walk.trails.has(edge.to.path)) {
|
|
24
25
|
walk.trails.set(edge.to.path, trail);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reach.js","sourceRoot":"","sources":["../../../src/imports/fences/reach.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reach.js","sourceRoot":"","sources":["../../../src/imports/fences/reach.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@ function reachFinding(file, reached) {
|
|
|
26
26
|
return {
|
|
27
27
|
file,
|
|
28
28
|
line: reached.line,
|
|
29
|
-
message: `Is a unit test but reaches ${
|
|
29
|
+
message: `Is a unit test but reaches ${reached.match} (${labelOf(reached.target)}) through ${reached.path.join(" -> ")}. A unit test reaches no engine, directly or through what it imports. ${KIT} ${repair(reached.path)}`,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
function standMessage(stand) {
|
|
@@ -65,7 +65,7 @@ export function unitTests(graph, sources) {
|
|
|
65
65
|
const edgesFrom = runtimeEdges(graph);
|
|
66
66
|
const byPath = new Map(sources.map((source) => [source.path, source]));
|
|
67
67
|
return (file) => {
|
|
68
|
-
const walked = walkFrom(file, edgesFrom,
|
|
68
|
+
const walked = walkFrom(file, edgesFrom, engineOf);
|
|
69
69
|
const checked = [...walked.visited].filter(([module]) => module === file || isTestSupport(module));
|
|
70
70
|
return [
|
|
71
71
|
...[...walked.reached].sort((a, b) => a.line - b.line).map((reached) => reachFinding(file, reached)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unitTests.js","sourceRoot":"","sources":["../../../src/rules/stories/unitTests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAA4B,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEjG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAc,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,QAAQ,GAAG,SAAS,CAAC;AAE3B,MAAM,GAAG,GAAG,kGAAkG,CAAC;AAE/G,SAAS,YAAY,CAAC,KAAkB;IACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU;SAC/B,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;SACvF,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,UAAmB,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IACjJ,OAAO,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACvG,CAAC;AAED,SAAS,MAAM,CAAC,IAAuB;IACtC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,OAAO,yCAAyC,CAAC;IAClD,CAAC;IACD,OAAO,aAAa,CAAC,KAAK,CAAC;QAC1B,CAAC,CAAC,6FAA6F,KAAK,GAAG;QACvG,CAAC,CAAC,SAAS,KAAK,qHAAqH,CAAC;AACxI,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"unitTests.js","sourceRoot":"","sources":["../../../src/rules/stories/unitTests.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usesDom.d.ts","sourceRoot":"","sources":["../../../src/rules/test-names/usesDom.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAiB3C,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"usesDom.d.ts","sourceRoot":"","sources":["../../../src/rules/test-names/usesDom.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAiB3C,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAUjD"}
|
|
@@ -2,17 +2,24 @@ import ts from "typescript";
|
|
|
2
2
|
import { parse } from "#rules/syntax.ts";
|
|
3
3
|
const DOM_GLOBALS = new Set(["document", "window"]);
|
|
4
4
|
const TESTING_LIBRARY = "@testing-library/";
|
|
5
|
-
function touchesDom(node) {
|
|
5
|
+
function touchesDom(node, checker) {
|
|
6
6
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
7
7
|
return node.moduleSpecifier.text.startsWith(TESTING_LIBRARY);
|
|
8
8
|
}
|
|
9
9
|
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && DOM_GLOBALS.has(node.expression.text)) {
|
|
10
|
-
return
|
|
10
|
+
return (checker.getSymbolAtLocation(node.expression)?.declarations ?? []).length === 0;
|
|
11
11
|
}
|
|
12
|
-
return ts.forEachChild(node, touchesDom) ?? false;
|
|
12
|
+
return ts.forEachChild(node, (child) => touchesDom(child, checker)) ?? false;
|
|
13
13
|
}
|
|
14
14
|
export function usesDom(file) {
|
|
15
15
|
const source = parse(file);
|
|
16
|
-
|
|
16
|
+
if (source === undefined) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const options = { allowJs: true, noLib: true, noResolve: true };
|
|
20
|
+
const host = ts.createCompilerHost(options);
|
|
21
|
+
host.getSourceFile = (path) => (path === source.fileName ? source : undefined);
|
|
22
|
+
const checker = ts.createProgram([source.fileName], options, host).getTypeChecker();
|
|
23
|
+
return touchesDom(source, checker);
|
|
17
24
|
}
|
|
18
25
|
//# sourceMappingURL=usesDom.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usesDom.js","sourceRoot":"","sources":["../../../src/rules/test-names/usesDom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEzE,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAE5C,SAAS,UAAU,CAAC,IAAa;
|
|
1
|
+
{"version":3,"file":"usesDom.js","sourceRoot":"","sources":["../../../src/rules/test-names/usesDom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEzE,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAE5C,SAAS,UAAU,CAAC,IAAa,EAAE,OAAuB;IACzD,IAAI,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9E,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtH,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAgB;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACpF,MAAM,IAAI,GAAG,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5C,IAAI,CAAC,aAAa,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;IACpF,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACpC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/quality",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.1",
|
|
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": {
|
|
@@ -2,8 +2,9 @@ import type { ImportEdge } from "#imports/graph.ts";
|
|
|
2
2
|
import type { Endpoint } from "#imports/resolve.ts";
|
|
3
3
|
import type { Matcher } from "./match.ts";
|
|
4
4
|
|
|
5
|
-
export interface Reached {
|
|
5
|
+
export interface Reached<TMatch = unknown> {
|
|
6
6
|
readonly line: number;
|
|
7
|
+
readonly match: TMatch;
|
|
7
8
|
readonly path: readonly string[];
|
|
8
9
|
readonly target: Endpoint;
|
|
9
10
|
}
|
|
@@ -31,32 +32,37 @@ export function outgoing(edges: readonly ImportEdge[]): ReadonlyMap<string, read
|
|
|
31
32
|
return from;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
interface Walk {
|
|
35
|
-
readonly forbidden:
|
|
35
|
+
interface Walk<TMatch> {
|
|
36
|
+
readonly forbidden: (endpoint: Endpoint) => TMatch | undefined;
|
|
36
37
|
readonly queue: string[];
|
|
37
|
-
readonly reached: Map<string, Reached
|
|
38
|
+
readonly reached: Map<string, Reached<TMatch>>;
|
|
38
39
|
readonly trails: Map<string, Trail>;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function follow(walk: Walk
|
|
42
|
+
function follow<TMatch>(walk: Walk<TMatch>, before: Trail, edge: ImportEdge): void {
|
|
42
43
|
const label = labelOf(edge.to);
|
|
43
44
|
const trail = { line: before.path.length === 1 ? edge.line : before.line, path: [...before.path, label] };
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const match = walk.forbidden(edge.to);
|
|
46
|
+
if (match) {
|
|
47
|
+
walk.reached.set(label, walk.reached.get(label) ?? { ...trail, match, target: edge.to });
|
|
46
48
|
} else if (edge.to.kind === "file" && !walk.trails.has(edge.to.path)) {
|
|
47
49
|
walk.trails.set(edge.to.path, trail);
|
|
48
50
|
walk.queue.push(edge.to.path);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
export interface Walked {
|
|
53
|
-
readonly reached: readonly Reached[];
|
|
54
|
+
export interface Walked<TMatch = unknown> {
|
|
55
|
+
readonly reached: readonly Reached<TMatch>[];
|
|
54
56
|
readonly visited: ReadonlyMap<string, Trail>;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
// A breadth-first walk that stops at each forbidden endpoint, so a finding names the shortest path to the first forbidden step.
|
|
58
|
-
export function walkFrom(
|
|
59
|
-
|
|
60
|
+
export function walkFrom<TMatch>(
|
|
61
|
+
start: string,
|
|
62
|
+
edgesFrom: ReadonlyMap<string, readonly ImportEdge[]>,
|
|
63
|
+
forbidden: (endpoint: Endpoint) => TMatch | undefined,
|
|
64
|
+
): Walked<TMatch> {
|
|
65
|
+
const walk: Walk<TMatch> = { forbidden, queue: [start], reached: new Map(), trails: new Map([[start, { line: 0, path: [start] }]]) };
|
|
60
66
|
for (const node of walk.queue) {
|
|
61
67
|
const before = walk.trails.get(node) ?? { line: 0, path: [node] };
|
|
62
68
|
for (const edge of edgesFrom.get(node) ?? []) {
|
|
@@ -29,11 +29,11 @@ function repair(path: readonly string[]): string {
|
|
|
29
29
|
: `split ${first}: move the pure code under test into its own module that reaches no engine, and test that module in the .unit file.`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function reachFinding(file: string, reached: Reached): Finding {
|
|
32
|
+
function reachFinding(file: string, reached: Reached<string>): Finding {
|
|
33
33
|
return {
|
|
34
34
|
file,
|
|
35
35
|
line: reached.line,
|
|
36
|
-
message: `Is a unit test but reaches ${
|
|
36
|
+
message: `Is a unit test but reaches ${reached.match} (${labelOf(reached.target)}) through ${reached.path.join(" -> ")}. A unit test reaches no engine, directly or through what it imports. ${KIT} ${repair(reached.path)}`,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -76,7 +76,7 @@ export function unitTests(graph: ImportGraph, sources: readonly SourceFile[]): (
|
|
|
76
76
|
const edgesFrom = runtimeEdges(graph);
|
|
77
77
|
const byPath = new Map(sources.map((source) => [source.path, source]));
|
|
78
78
|
return (file) => {
|
|
79
|
-
const walked = walkFrom(file, edgesFrom,
|
|
79
|
+
const walked = walkFrom(file, edgesFrom, engineOf);
|
|
80
80
|
const checked = [...walked.visited].filter(([module]) => module === file || isTestSupport(module));
|
|
81
81
|
return [
|
|
82
82
|
...[...walked.reached].sort((a, b) => a.line - b.line).map((reached) => reachFinding(file, reached)),
|
|
@@ -6,17 +6,24 @@ const DOM_GLOBALS: ReadonlySet<string> = new Set(["document", "window"]);
|
|
|
6
6
|
|
|
7
7
|
const TESTING_LIBRARY = "@testing-library/";
|
|
8
8
|
|
|
9
|
-
function touchesDom(node: ts.Node): boolean {
|
|
9
|
+
function touchesDom(node: ts.Node, checker: ts.TypeChecker): boolean {
|
|
10
10
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
11
11
|
return node.moduleSpecifier.text.startsWith(TESTING_LIBRARY);
|
|
12
12
|
}
|
|
13
13
|
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && DOM_GLOBALS.has(node.expression.text)) {
|
|
14
|
-
return
|
|
14
|
+
return (checker.getSymbolAtLocation(node.expression)?.declarations ?? []).length === 0;
|
|
15
15
|
}
|
|
16
|
-
return ts.forEachChild(node, touchesDom) ?? false;
|
|
16
|
+
return ts.forEachChild(node, (child) => touchesDom(child, checker)) ?? false;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function usesDom(file: SourceFile): boolean {
|
|
20
20
|
const source = parse(file);
|
|
21
|
-
|
|
21
|
+
if (source === undefined) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const options: ts.CompilerOptions = { allowJs: true, noLib: true, noResolve: true };
|
|
25
|
+
const host = ts.createCompilerHost(options);
|
|
26
|
+
host.getSourceFile = (path) => (path === source.fileName ? source : undefined);
|
|
27
|
+
const checker = ts.createProgram([source.fileName], options, host).getTypeChecker();
|
|
28
|
+
return touchesDom(source, checker);
|
|
22
29
|
}
|