@cplieger/deadset-ts 8.0.0-dev.6 → 8.0.0-dev.8
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/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +37 -56
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +6 -82
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/consumers.js +2 -2
- package/dist/src/contract/config.generated.js +544 -0
- package/dist/src/contract/contract.generated.js +13 -0
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +14 -40
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +0 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +5 -4
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +1 -1
- package/dist/src/findings/non-code-artifacts.js +2 -2
- package/dist/src/findings/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -2
- package/dist/src/findings/unused-declarations.js +1 -1
- package/dist/src/findings/visibility-narrowing.js +2 -2
- package/dist/src/findings-pass.js +7 -12
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +59 -0
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +0 -7
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +3 -225
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -724
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +55 -61
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +34 -103
- package/dist/src/serialization-contract.js +5 -9
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +21 -25
- package/dist/src/suppress.js +3 -3
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +25 -26
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +8 -6
- package/dist/src/verbs/print-projects.js +4 -3
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +55 -76
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +31 -296
- package/src/configuration-files.ts +27 -170
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +4 -4
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/contract.generated.ts +25 -0
- package/src/contract/report.generated.ts +3 -2
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +15 -40
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +0 -5
- package/src/exempt.ts +8 -30
- package/src/file-facts.ts +6 -24
- package/src/findings/completion.ts +5 -6
- package/src/findings/emitter.ts +2 -2
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +1 -1
- package/src/findings/non-code-artifacts.ts +4 -4
- package/src/findings/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +9 -15
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +74 -0
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +0 -8
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -92
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +3 -301
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +283 -1114
- package/src/roots.ts +7 -20
- package/src/run.ts +57 -75
- package/src/schema.ts +391 -388
- package/src/scope.ts +40 -126
- package/src/serialization-contract.ts +5 -10
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +21 -29
- package/src/suppress.ts +3 -3
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +32 -42
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +8 -10
- package/src/verbs/print-projects.ts +4 -3
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/verb.ts +2 -2
- package/src/verbs/version.ts +2 -1
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Every row carries a reason, because the list is read where the rows are shown
|
|
8
8
|
* one at a time: beside a finding, or in a report's declared gaps.
|
|
9
9
|
*/
|
|
10
|
+
import { bytewise } from "./text/compare.js";
|
|
10
11
|
/** What is declined for a tool whose own conventions are entered only through its configuration files. */
|
|
11
12
|
const EVERY_CONVENTION = "every file it loads by its own convention beyond its configuration files";
|
|
12
13
|
/** Why a tool whose own conventions are not read declines them. */
|
|
@@ -291,15 +292,8 @@ const PARTLY_ENTERED = [
|
|
|
291
292
|
reason: NOT_LITERAL,
|
|
292
293
|
},
|
|
293
294
|
];
|
|
294
|
-
/** Two strings ordered bytewise. */
|
|
295
|
-
function compare(a, b) {
|
|
296
|
-
if (a === b) {
|
|
297
|
-
return 0;
|
|
298
|
-
}
|
|
299
|
-
return a < b ? -1 : 1;
|
|
300
|
-
}
|
|
301
295
|
/** Every declined entry-point convention, ordered by tool and then by convention. */
|
|
302
296
|
export const DECLINED_CONVENTIONS = [
|
|
303
297
|
...UNREAD_TOOLS.map((tool) => ({ tool, convention: EVERY_CONVENTION, reason: UNREAD })),
|
|
304
298
|
...PARTLY_ENTERED,
|
|
305
|
-
].sort((a, b) =>
|
|
299
|
+
].sort((a, b) => bytewise(a.tool, b.tool) || bytewise(a.convention, b.convention));
|
package/dist/src/entry-points.js
CHANGED
|
@@ -10,17 +10,18 @@
|
|
|
10
10
|
* value that is not a literal is not evaluated and not guessed, so a file it would
|
|
11
11
|
* have named is entered by nothing here.
|
|
12
12
|
*/
|
|
13
|
-
import { isArrayLiteralExpression,
|
|
13
|
+
import { isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
14
14
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
15
15
|
import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
|
|
16
16
|
import { buildScriptEntries } from "./build-scripts.js";
|
|
17
|
-
import { globExpression } from "./glob.js";
|
|
18
17
|
import { htmlEntries } from "./html-entries.js";
|
|
19
18
|
import { documentEntries } from "./mdx-entries.js";
|
|
20
19
|
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
21
20
|
import { UNANSWERED } from "./query.js";
|
|
22
21
|
import { computedLoadDirectory, leadingLiteral } from "./unseen-loads.js";
|
|
23
22
|
import { workflowEntries } from "./workflow-steps.js";
|
|
23
|
+
import { unwrapped } from "./unwrap.js";
|
|
24
|
+
import { globExpression } from "./text/glob.js";
|
|
24
25
|
/** The extensions the test runner and the browser-test runner load a configuration from. */
|
|
25
26
|
const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"];
|
|
26
27
|
/** The tool file names the configuration form does not cover. */
|
|
@@ -69,14 +70,6 @@ function literalOf(node) {
|
|
|
69
70
|
}
|
|
70
71
|
return undefined;
|
|
71
72
|
}
|
|
72
|
-
/** The expression a wrapper that changes no value holds: parentheses and type assertions. */
|
|
73
|
-
function unwrapped(node) {
|
|
74
|
-
let at = node;
|
|
75
|
-
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
76
|
-
at = at.expression;
|
|
77
|
-
}
|
|
78
|
-
return at;
|
|
79
|
-
}
|
|
80
73
|
/**
|
|
81
74
|
* The object literals one configuration file's default export is written as: the
|
|
82
75
|
* literal itself, or each literal argument of the call it is, at any depth of calls.
|
package/dist/src/enum-group.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* or `unknown` expression asserted to it, or bound to a variable or returned from a
|
|
7
7
|
* function it annotates, where a number counts too. An enum's own value converts nothing.
|
|
8
8
|
*/
|
|
9
|
-
import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral,
|
|
9
|
+
import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
10
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
10
11
|
import { TypeFlags } from "@typescript/native/sync";
|
|
11
12
|
import { aliasChains } from "./alias-chain.js";
|
|
12
13
|
import { nodeKey } from "./inventory.js";
|
|
@@ -16,14 +17,6 @@ import { isAnswered, UNANSWERED } from "./query.js";
|
|
|
16
17
|
const NUMBER = TypeFlags.NumberLike;
|
|
17
18
|
const STRING = TypeFlags.StringLike;
|
|
18
19
|
const DECODED = TypeFlags.Any | TypeFlags.Unknown;
|
|
19
|
-
/** The expression one node is, through however many parentheses are written around it. */
|
|
20
|
-
function unparenthesized(node) {
|
|
21
|
-
let held = node;
|
|
22
|
-
while (isParenthesizedExpression(held)) {
|
|
23
|
-
held = held.expression;
|
|
24
|
-
}
|
|
25
|
-
return held;
|
|
26
|
-
}
|
|
27
20
|
/**
|
|
28
21
|
* The name node a type node names its declaration by: the identifier of a type
|
|
29
22
|
* reference, the right side of a qualified one, through parentheses. A type that is no
|
|
@@ -45,7 +38,7 @@ function typeName(type) {
|
|
|
45
38
|
}
|
|
46
39
|
/** The name node an expression names an object by: an identifier, or a property access's name. */
|
|
47
40
|
function objectName(expression) {
|
|
48
|
-
const held =
|
|
41
|
+
const held = parenthesesRemoved(expression);
|
|
49
42
|
if (isIdentifier(held)) {
|
|
50
43
|
return held;
|
|
51
44
|
}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* either stores. A store into an erased property of a type the analyzed program does not
|
|
7
7
|
* declare hands the value to the code that declares it.
|
|
8
8
|
*/
|
|
9
|
-
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression,
|
|
9
|
+
import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
10
10
|
import { SymbolFlags, TypeFlags } from "@typescript/native/sync";
|
|
11
11
|
import { must } from "./query.js";
|
|
12
12
|
import { checkedTypes } from "./value-reach.js";
|
|
13
|
+
import { parenthesesRemoved } from "./unwrap.js";
|
|
13
14
|
/** The types that keep nothing of the value they are given. */
|
|
14
15
|
const ERASED = TypeFlags.Any | TypeFlags.Unknown;
|
|
15
16
|
/** The array methods whose arguments become elements of the array they are called on. */
|
|
@@ -20,9 +21,6 @@ function literalKey(node) {
|
|
|
20
21
|
? node.text
|
|
21
22
|
: undefined;
|
|
22
23
|
}
|
|
23
|
-
function unwrapped(node) {
|
|
24
|
-
return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
|
|
25
|
-
}
|
|
26
24
|
/**
|
|
27
25
|
* Whether a variable declaration's type can differ from what is stored in it: it carries an
|
|
28
26
|
* annotation, or it has neither an annotation nor an initializer, so its type evolves.
|
|
@@ -76,8 +74,8 @@ function propertyAt(source, name) {
|
|
|
76
74
|
* here, and a default value is stored in its target too.
|
|
77
75
|
*/
|
|
78
76
|
function patternTargets(pattern, from, out) {
|
|
79
|
-
const target =
|
|
80
|
-
const source = from.path.length === 0 ?
|
|
77
|
+
const target = parenthesesRemoved(pattern);
|
|
78
|
+
const source = from.path.length === 0 ? parenthesesRemoved(from.value) : from.value;
|
|
81
79
|
const into = (element, step, literal) => {
|
|
82
80
|
patternTargets(element, literal === undefined
|
|
83
81
|
? { value: from.value, path: [...from.path, step] }
|
|
@@ -11,7 +11,3 @@ const VISIBILITY = new Map(TS_ROWS.map((row) => [row.class, row.typescriptVisibi
|
|
|
11
11
|
export function typescriptVisibilityOf(exemptionClass) {
|
|
12
12
|
return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
|
|
13
13
|
}
|
|
14
|
-
/** Whether one name is a class of the vocabulary, whichever language it runs on. */
|
|
15
|
-
export function isExemptionClass(name) {
|
|
16
|
-
return EXEMPTION_CLASSES.some((row) => row.class === name);
|
|
17
|
-
}
|
package/dist/src/exempt.js
CHANGED
|
@@ -7,26 +7,17 @@
|
|
|
7
7
|
* hands it the table of the classes it has.
|
|
8
8
|
*/
|
|
9
9
|
import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
|
|
10
|
-
import {
|
|
11
|
-
import { isExemptionClass, TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
|
|
10
|
+
import { TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
|
|
12
11
|
import { byPosition, positionKey } from "./position.js";
|
|
13
12
|
import { Unanswerable } from "./query.js";
|
|
14
13
|
import { sweep } from "./sweep.js";
|
|
14
|
+
import { bytewise } from "./text/compare.js";
|
|
15
15
|
/**
|
|
16
|
-
* The classes `exemptions.disabled` switches off. A
|
|
17
|
-
*
|
|
18
|
-
* switches nothing off here, so one configuration serves every analyzer.
|
|
16
|
+
* The classes `exemptions.disabled` switches off. A class that runs on another language
|
|
17
|
+
* only switches nothing off here, so one configuration serves every analyzer.
|
|
19
18
|
*/
|
|
20
19
|
export function disabledClasses(config) {
|
|
21
|
-
|
|
22
|
-
for (const name of config.exemptionsDisabled) {
|
|
23
|
-
if (!isExemptionClass(name)) {
|
|
24
|
-
const known = EXEMPTION_CLASSES.map((row) => row.class).join(", ");
|
|
25
|
-
throw new ConfigError("malformed", "exemptions.disabled", `exemptions.disabled: ${JSON.stringify(name)} is not an exemption class: the classes are ${known}`);
|
|
26
|
-
}
|
|
27
|
-
disabled.add(name);
|
|
28
|
-
}
|
|
29
|
-
return disabled;
|
|
20
|
+
return new Set(config.exemptions.disabled);
|
|
30
21
|
}
|
|
31
22
|
/**
|
|
32
23
|
* Whether a class whose row states `allowed` may retain a member of one visibility. A
|
|
@@ -122,20 +113,13 @@ export function heldRecords(records, holding) {
|
|
|
122
113
|
}
|
|
123
114
|
return { records: exemptionsOf(found), inTestFiles };
|
|
124
115
|
}
|
|
125
|
-
/** Two strings ordered bytewise. */
|
|
126
|
-
function compare(a, b) {
|
|
127
|
-
if (a === b) {
|
|
128
|
-
return 0;
|
|
129
|
-
}
|
|
130
|
-
return a < b ? -1 : 1;
|
|
131
|
-
}
|
|
132
116
|
/**
|
|
133
117
|
* Records as a run holds them: ordered by site, then class, then declaration, and one
|
|
134
118
|
* per declaration, class, detail and file it holds while live, the one at the first
|
|
135
119
|
* site. A fact found at many sites, or by several projects, is one record.
|
|
136
120
|
*/
|
|
137
121
|
export function exemptionsOf(found) {
|
|
138
|
-
const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) ||
|
|
122
|
+
const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id));
|
|
139
123
|
const seen = new Set();
|
|
140
124
|
return ordered.filter((record) => {
|
|
141
125
|
const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
|
package/dist/src/file-facts.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* file below the target root, whether or not a project includes it, and the files a
|
|
4
4
|
* program holds by inclusion rather than by an import.
|
|
5
5
|
*/
|
|
6
|
-
import { IGNORED_DIRECTORIES } from "./discover.js";
|
|
7
6
|
import { packageScope } from "./inventory.js";
|
|
8
7
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
9
8
|
import { renderRef } from "./ref.js";
|
|
9
|
+
import { filesBelow } from "./fs/tree.js";
|
|
10
10
|
/** A file name the compiler reads as a source file. */
|
|
11
11
|
const SOURCE_FILE = /\.(?:ts|tsx|mts|cts)$/u;
|
|
12
12
|
/** A file name the compiler reads as a declaration file, which no build emits code from. */
|
|
@@ -30,30 +30,7 @@ function lastLine(text) {
|
|
|
30
30
|
* that cannot be read is skipped, as discovery skips it.
|
|
31
31
|
*/
|
|
32
32
|
export function sourcePaths(host, targetRoot) {
|
|
33
|
-
|
|
34
|
-
const pending = [""];
|
|
35
|
-
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
36
|
-
let entries;
|
|
37
|
-
try {
|
|
38
|
-
entries = host.readDirectory(joinPath(targetRoot, dir));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
45
|
-
if (entry.directory) {
|
|
46
|
-
if (!IGNORED_DIRECTORIES.has(entry.name)) {
|
|
47
|
-
pending.push(path);
|
|
48
|
-
}
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (SOURCE_FILE.test(entry.name) && !DECLARATION_FILE.test(entry.name)) {
|
|
52
|
-
found.push(path);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return found.sort((a, b) => (a < b ? -1 : 1));
|
|
33
|
+
return filesBelow(host, targetRoot, (name) => SOURCE_FILE.test(name) && !DECLARATION_FILE.test(name));
|
|
57
34
|
}
|
|
58
35
|
/**
|
|
59
36
|
* Every source file below the target root, in path order. A file that cannot be read is
|
|
@@ -41,8 +41,9 @@ function everyDeclaredLoaded(boundary) {
|
|
|
41
41
|
* naming its family, else the code's default.
|
|
42
42
|
*/
|
|
43
43
|
function severityOf(input, code, fallback) {
|
|
44
|
-
const {
|
|
45
|
-
|
|
44
|
+
const { severity } = input.config;
|
|
45
|
+
const key = [code, code.slice(0, FAMILY_KEY_LENGTH)].find((one) => Object.hasOwn(severity, one));
|
|
46
|
+
return key === undefined ? fallback : (severity[key] ?? fallback);
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* The declarations the roots name as a library's published API, each public member of a
|
|
@@ -165,7 +166,7 @@ function testUsedSubjects(input) {
|
|
|
165
166
|
function rootsCeiling(input, finding, candidate, configurations) {
|
|
166
167
|
const unreachable = candidate?.relation === "reachability" || finding.unreachableOnly === true;
|
|
167
168
|
const unseen = new Set((input.files.unseenLoads ?? []).map((load) => load.configuration));
|
|
168
|
-
const complete = input.config.ts.
|
|
169
|
+
const complete = input.config.ts.roots_complete && !configurations.some((one) => unseen.has(one));
|
|
169
170
|
return unreachable && !complete ? "probable" : "certain";
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
@@ -460,7 +461,7 @@ function dialed(finding, minConfidence) {
|
|
|
460
461
|
* root withholds itself alone.
|
|
461
462
|
*/
|
|
462
463
|
export function dialsOf(input, findings) {
|
|
463
|
-
const minConfidence = input.config.analysis.
|
|
464
|
+
const minConfidence = input.config.analysis.min_confidence;
|
|
464
465
|
const components = new Set(findings
|
|
465
466
|
.filter((finding) => finding.component.root &&
|
|
466
467
|
dialed({ ...finding, confidence: componentConfidenceOf(finding) }, minConfidence))
|
|
@@ -86,7 +86,7 @@ function atPossible(input) {
|
|
|
86
86
|
const { config } = input;
|
|
87
87
|
return {
|
|
88
88
|
...input,
|
|
89
|
-
config: { ...config, analysis: { ...config.analysis,
|
|
89
|
+
config: { ...config, analysis: { ...config.analysis, min_confidence: "possible" } },
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
@@ -98,7 +98,7 @@ export const intraFunction = (input) => {
|
|
|
98
98
|
return symbol === undefined ? [] : [symbol.id];
|
|
99
99
|
}));
|
|
100
100
|
const { declared, loaded } = input.boundary.consumers;
|
|
101
|
-
const closed = input.config.
|
|
101
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
102
102
|
const published = (id) => {
|
|
103
103
|
const symbol = byId.get(id);
|
|
104
104
|
return (symbol !== undefined &&
|
|
@@ -10,8 +10,8 @@ const FILE_START = 1;
|
|
|
10
10
|
* derived matrix is never complete.
|
|
11
11
|
*/
|
|
12
12
|
function completeMatrix(config) {
|
|
13
|
-
return (config.analysis.
|
|
14
|
-
config.analysis.configurations.some((entry) =>
|
|
13
|
+
return (config.analysis.matrix.complete &&
|
|
14
|
+
config.analysis.configurations.some((entry) => "project" in entry));
|
|
15
15
|
}
|
|
16
16
|
/** One finding about a whole file. */
|
|
17
17
|
function fileFinding(code, file, message) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CODE } from "../contract/contract.generated.js";
|
|
2
2
|
import { positionKey } from "../position.js";
|
|
3
3
|
import { declarationKey } from "../ref.js";
|
|
4
|
+
import { bytewise } from "../text/compare.js";
|
|
4
5
|
/** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
|
|
5
6
|
const SIGNATURES = new Set(["function", "method", "type"]);
|
|
6
7
|
function indexed(input) {
|
|
@@ -76,7 +77,7 @@ function writeOnlyMessage(run, namedBy, symbol, held) {
|
|
|
76
77
|
: "never read";
|
|
77
78
|
const cascade = [
|
|
78
79
|
...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
79
|
-
...[...held.alone].sort().map((name) => `import ${name}`),
|
|
80
|
+
...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
|
|
80
81
|
];
|
|
81
82
|
const word = SUBJECT_WORDS[symbol.kind] ?? "member";
|
|
82
83
|
const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
|
|
@@ -2,6 +2,7 @@ import { CODE } from "../contract/contract.generated.js";
|
|
|
2
2
|
import { REPOSITORY_DOCUMENT } from "../config.js";
|
|
3
3
|
import { FIXED_SEVERITY_CODES } from "../kinds.js";
|
|
4
4
|
import { isDependencyCode, isUnsuppressibleCode, } from "../suppress.js";
|
|
5
|
+
import { bytewise } from "../text/compare.js";
|
|
5
6
|
/** The subject kind of a finding about a configured declaration. */
|
|
6
7
|
const DECLARATION_SUBJECT = "configured-declaration";
|
|
7
8
|
/** The subject kind of a finding about a suppression record. */
|
|
@@ -80,10 +81,10 @@ export function staleSuppressions(stale, files) {
|
|
|
80
81
|
bySite.set(key, held);
|
|
81
82
|
}
|
|
82
83
|
return [...bySite.values()].map(({ first, records }) => {
|
|
83
|
-
const codes = [...new Set(records.map((one) => one.record.code))].sort();
|
|
84
|
+
const codes = [...new Set(records.map((one) => one.record.code))].sort(bytewise);
|
|
84
85
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
85
86
|
.filter((code) => !codes.includes(code))
|
|
86
|
-
.sort();
|
|
87
|
+
.sort(bytewise);
|
|
87
88
|
const rows = codes.filter(isUnsuppressibleCode);
|
|
88
89
|
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
89
90
|
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
@@ -178,7 +178,7 @@ export const unusedDeclarations = (input) => [
|
|
|
178
178
|
* remove unless the run includes generated files.
|
|
179
179
|
*/
|
|
180
180
|
function unusedImports(input) {
|
|
181
|
-
const generated = input.config.analysis.
|
|
181
|
+
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
182
182
|
return input.intraFunction.imports.flatMap((binding) => binding.used || generated?.has(binding.position.path) === true
|
|
183
183
|
? []
|
|
184
184
|
: [
|
|
@@ -193,10 +193,10 @@ function unreachable(facts, at, symbol, candidate, input) {
|
|
|
193
193
|
export function narrowings(input) {
|
|
194
194
|
const facts = factsOf(input.swept.matrix.union);
|
|
195
195
|
const { declared, loaded } = input.boundary.consumers;
|
|
196
|
-
const closed = input.config.
|
|
196
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
197
197
|
const world = { closed, writeOnly: writeOnlyDeclarations(input) };
|
|
198
198
|
const candidates = new Map(input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
199
|
-
const generated = input.config.analysis.
|
|
199
|
+
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
200
200
|
const findings = [];
|
|
201
201
|
facts.graph.symbols.forEach((symbol, at) => {
|
|
202
202
|
if (generated?.has(symbol.position.path) === true) {
|
|
@@ -8,20 +8,15 @@ import { fails } from "./config.js";
|
|
|
8
8
|
import { decidedFindings } from "./findings/emitters.js";
|
|
9
9
|
import { fileRefsOf, staleSuppressions, } from "./findings/self-check.js";
|
|
10
10
|
import { isDependencySubject, isRowSubject, pairs, suppressionCounts, } from "./suppress.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (a === b) {
|
|
14
|
-
return 0;
|
|
15
|
-
}
|
|
16
|
-
return a < b ? -1 : 1;
|
|
17
|
-
}
|
|
11
|
+
import { bytewise } from "./text/compare.js";
|
|
12
|
+
import { PRODUCT } from "./product.js";
|
|
18
13
|
/** The canonical order: path, line, column, code, then the symbol reference. */
|
|
19
14
|
function canonical(a, b) {
|
|
20
|
-
return (
|
|
15
|
+
return (bytewise(a.position.path, b.position.path) ||
|
|
21
16
|
a.position.line - b.position.line ||
|
|
22
17
|
a.position.column - b.position.column ||
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
bytewise(a.code, b.code) ||
|
|
19
|
+
bytewise(a.symbol.ref, b.symbol.ref));
|
|
25
20
|
}
|
|
26
21
|
/**
|
|
27
22
|
* The pass over one run. The emitter table decides every record against every finding the
|
|
@@ -71,7 +66,7 @@ export function verdictOf(result, config) {
|
|
|
71
66
|
if (result.totals.staleSuppressions > 0) {
|
|
72
67
|
return EXIT_FINDINGS;
|
|
73
68
|
}
|
|
74
|
-
const failing = result.findings.some((finding) => fails(finding.severity, config.reporters.
|
|
69
|
+
const failing = result.findings.some((finding) => fails(finding.severity, config.reporters.fail_on));
|
|
75
70
|
return failing ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
76
71
|
}
|
|
77
72
|
/**
|
|
@@ -98,5 +93,5 @@ export function rowKey(row) {
|
|
|
98
93
|
}
|
|
99
94
|
/** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
|
|
100
95
|
export function analyzerProvenance(version) {
|
|
101
|
-
return { analyzer:
|
|
96
|
+
return { analyzer: PRODUCT, version };
|
|
102
97
|
}
|
|
@@ -16,8 +16,8 @@ import { must } from "./query.js";
|
|
|
16
16
|
/** Every contract's components and bases, resolved in one pass over the entries. */
|
|
17
17
|
function resolveContracts(project, held, contracts) {
|
|
18
18
|
const entries = contracts.flatMap((contract) => [
|
|
19
|
-
...contract.components,
|
|
20
|
-
...contract.bases,
|
|
19
|
+
...(contract.components ?? []),
|
|
20
|
+
...(contract.bases ?? []),
|
|
21
21
|
]);
|
|
22
22
|
const resolved = resolveEntries(project, held, entries);
|
|
23
23
|
let next = 0;
|
|
@@ -28,8 +28,8 @@ function resolveContracts(project, held, contracts) {
|
|
|
28
28
|
};
|
|
29
29
|
return contracts.map((contract) => ({
|
|
30
30
|
contract,
|
|
31
|
-
components: take(contract.components
|
|
32
|
-
bases: take(contract.bases
|
|
31
|
+
components: take(contract.components?.length ?? 0),
|
|
32
|
+
bases: take(contract.bases?.length ?? 0),
|
|
33
33
|
}));
|
|
34
34
|
}
|
|
35
35
|
/** The classes a decorator or a call the contracts' components name makes a component. */
|
|
@@ -160,11 +160,11 @@ function byBases(project, held, chains, contracts) {
|
|
|
160
160
|
*/
|
|
161
161
|
export function frameworkLifecycle(input) {
|
|
162
162
|
const { project, held, targetRoot } = input;
|
|
163
|
-
if (input.ts.
|
|
163
|
+
if (input.ts.lifecycle_contracts.length === 0) {
|
|
164
164
|
return [];
|
|
165
165
|
}
|
|
166
166
|
const chains = aliasChains(project, held);
|
|
167
|
-
const contracts = resolveContracts(project, held, input.ts.
|
|
167
|
+
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
168
168
|
const components = [
|
|
169
169
|
...byComponents(project, held, chains, contracts),
|
|
170
170
|
...byBases(project, held, chains, contracts),
|
|
@@ -173,7 +173,7 @@ export function frameworkLifecycle(input) {
|
|
|
173
173
|
return [];
|
|
174
174
|
}
|
|
175
175
|
const ids = new Set(components.map((one) => one.id));
|
|
176
|
-
const sides = input.ts.
|
|
176
|
+
const sides = input.ts.lifecycle_contracts.some((contract) => contract.members.some((name) => name.endsWith(":static")))
|
|
177
177
|
? ["instance", "static"]
|
|
178
178
|
: ["instance"];
|
|
179
179
|
const members = sides.map((side) => classMembers(project, held, ids, side));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one read of a document the target holds at its root, which a checkout under analysis
|
|
3
|
+
* controls: the repository configuration, the ignore file, the baseline and the edges
|
|
4
|
+
* document. The read stays inside the target root, takes a regular file alone, and holds no
|
|
5
|
+
* more of it than the Contract's document bound.
|
|
6
|
+
*/
|
|
7
|
+
import { CONFIG_MAX_BYTES } from "../contract/config.generated.js";
|
|
8
|
+
import { joinPath, relativePath } from "../paths.js";
|
|
9
|
+
import { message } from "../product.js";
|
|
10
|
+
import { utf8Bytes } from "../reporters/sha256.js";
|
|
11
|
+
import { UsageError } from "../usage.js";
|
|
12
|
+
/**
|
|
13
|
+
* A document at the target root that is not one to read: its name resolves outside the
|
|
14
|
+
* target root or to anything but a regular file, or the read fails. Nothing is read from
|
|
15
|
+
* it, and the run ends with the failure code.
|
|
16
|
+
*/
|
|
17
|
+
export class TargetDocumentError extends Error {
|
|
18
|
+
constructor(label, reason) {
|
|
19
|
+
super(`${label}: ${reason}`);
|
|
20
|
+
this.name = "TargetDocumentError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
|
|
24
|
+
export function refuseOverBound(label, text) {
|
|
25
|
+
if (utf8Bytes(text).length > CONFIG_MAX_BYTES) {
|
|
26
|
+
throw new UsageError(`${label}: holds more than ${String(CONFIG_MAX_BYTES)} bytes`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** What `read` returns, or its throw as the document's {@link TargetDocumentError}. */
|
|
30
|
+
function attempt(label, read) {
|
|
31
|
+
try {
|
|
32
|
+
return read();
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new TargetDocumentError(label, message(error));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
|
|
40
|
+
* there, each refusal naming it `label`. A document past the bound is read one byte past
|
|
41
|
+
* it and refused as {@link refuseOverBound} refuses it.
|
|
42
|
+
*/
|
|
43
|
+
export function readTargetDocument(host, targetRoot, name, label = name) {
|
|
44
|
+
const path = joinPath(targetRoot, name);
|
|
45
|
+
const kind = attempt(label, () => host.kindOf(path));
|
|
46
|
+
if (kind === "absent") {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const real = attempt(label, () => host.realPath(path));
|
|
50
|
+
if (relativePath(attempt(label, () => host.realPath(targetRoot)), real) === undefined) {
|
|
51
|
+
throw new TargetDocumentError(label, `resolves to ${real}, outside the target root`);
|
|
52
|
+
}
|
|
53
|
+
if (kind !== "file") {
|
|
54
|
+
throw new TargetDocumentError(label, "is not a regular file");
|
|
55
|
+
}
|
|
56
|
+
const text = attempt(label, () => host.readFile(real, CONFIG_MAX_BYTES + 1));
|
|
57
|
+
refuseOverBound(label, text);
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isRecord } from "../json-document.js";
|
|
2
|
+
import { joinPath } from "../paths.js";
|
|
3
|
+
/** The file a package's manifest is read from. */
|
|
4
|
+
export const MANIFEST = "package.json";
|
|
5
|
+
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
6
|
+
export const INSTALLED_PACKAGES = "node_modules";
|
|
7
|
+
/** Whether one path passes through an installed-packages directory. */
|
|
8
|
+
export function inInstalledPackages(path) {
|
|
9
|
+
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
10
|
+
}
|
|
11
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
12
|
+
export function readDocument(host, path) {
|
|
13
|
+
let text;
|
|
14
|
+
try {
|
|
15
|
+
text = host.readFile(path);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return { text, value: JSON.parse(text) };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
28
|
+
export function readJSON(host, path) {
|
|
29
|
+
return readDocument(host, path)?.value;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The manifest in one directory, decoded, or undefined where none reads as an object. A
|
|
33
|
+
* manifest this program cannot read declares nothing, the same answer as an absent one:
|
|
34
|
+
* the package manager owns the refusal of a manifest it rejects.
|
|
35
|
+
*/
|
|
36
|
+
export function manifestAt(host, dir) {
|
|
37
|
+
const value = readJSON(host, joinPath(dir, MANIFEST));
|
|
38
|
+
return isRecord(value) ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Whether one directory holds a manifest file. */
|
|
41
|
+
export function holdsManifest(host, dir) {
|
|
42
|
+
return host.kindOf(joinPath(dir, MANIFEST)) === "file";
|
|
43
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ancestors, joinPath, resolvePath } from "../paths.js";
|
|
2
|
+
import { LANGUAGE_ROWS } from "../contract/contract.generated.js";
|
|
3
|
+
import { LANGUAGE } from "../product.js";
|
|
4
|
+
import { bytewise } from "../text/compare.js";
|
|
5
|
+
import { globExpression } from "../text/glob.js";
|
|
6
|
+
/** The directories no walk of the tree enters, by the patterns the language's row names. */
|
|
7
|
+
const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
|
|
8
|
+
function skipped(name) {
|
|
9
|
+
return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
|
|
10
|
+
}
|
|
11
|
+
/** One file's text without its line ending, or undefined where it cannot be read. */
|
|
12
|
+
function lineOf(host, path) {
|
|
13
|
+
try {
|
|
14
|
+
return host.readFile(path).replace(/\r?\n$/u, "");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The repository whose working tree holds `dir`: the nearest directory at or above it with a
|
|
22
|
+
* `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
|
|
23
|
+
* directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
|
|
24
|
+
* the directory shared with the main worktree, which holds `info/exclude`:
|
|
25
|
+
* https://git-scm.com/docs/gitrepository-layout
|
|
26
|
+
*/
|
|
27
|
+
export function repositoryOf(host, dir) {
|
|
28
|
+
for (const root of ancestors(dir)) {
|
|
29
|
+
const dotGit = joinPath(root, ".git");
|
|
30
|
+
const kind = host.kindOf(dotGit);
|
|
31
|
+
if (kind === "directory") {
|
|
32
|
+
return { root, exclude: joinPath(dotGit, "info/exclude") };
|
|
33
|
+
}
|
|
34
|
+
if (kind === "file") {
|
|
35
|
+
const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
|
|
36
|
+
const gitDir = named === undefined ? undefined : resolvePath(root, named);
|
|
37
|
+
const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
|
|
38
|
+
const shared = gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
|
|
39
|
+
return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
/** The entries of one directory, none where it cannot be read. */
|
|
45
|
+
export function entriesOf(host, dir) {
|
|
46
|
+
try {
|
|
47
|
+
return host.readDirectory(dir);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
|
|
54
|
+
export function filesIn(host, dir, accepts) {
|
|
55
|
+
return entriesOf(host, dir)
|
|
56
|
+
.filter((entry) => !entry.directory && accepts(entry.name))
|
|
57
|
+
.map((entry) => entry.name)
|
|
58
|
+
.sort(bytewise);
|
|
59
|
+
}
|
|
60
|
+
/** The names of the directories directly in `dir` a walk enters, in bytewise order. */
|
|
61
|
+
export function directoriesIn(host, dir) {
|
|
62
|
+
return entriesOf(host, dir)
|
|
63
|
+
.filter((entry) => entry.directory && !skipped(entry.name))
|
|
64
|
+
.map((entry) => entry.name)
|
|
65
|
+
.sort(bytewise);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
|
|
69
|
+
* order, read in no skipped directory.
|
|
70
|
+
*/
|
|
71
|
+
export function filesBelow(host, root, accepts) {
|
|
72
|
+
const found = [];
|
|
73
|
+
const pending = [""];
|
|
74
|
+
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
75
|
+
for (const entry of entriesOf(host, joinPath(root, dir))) {
|
|
76
|
+
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
77
|
+
if (!entry.directory) {
|
|
78
|
+
if (accepts(entry.name)) {
|
|
79
|
+
found.push(path);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (!skipped(entry.name)) {
|
|
83
|
+
pending.push(path);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return found.sort(bytewise);
|
|
88
|
+
}
|