vibecodedshit 0.0.0-stage → 0.2.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/LICENSE +95 -0
- package/README.md +64 -2
- package/assets/fonts/NotoEmoji-OFL.txt +93 -0
- package/assets/fonts/NotoEmoji-Poop.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-Bold.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-OFL.txt +93 -0
- package/dist/bin.js +8 -0
- package/dist/card/previous.js +38 -0
- package/dist/card/render.js +71 -0
- package/dist/cli.js +121 -0
- package/dist/commands/card.js +69 -0
- package/dist/commands/common.js +1 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/footer.js +25 -0
- package/dist/commands/report.js +49 -0
- package/dist/commands/route.js +62 -0
- package/dist/commands/submit.js +125 -0
- package/dist/commands/summary.js +77 -0
- package/dist/diff/compare.js +73 -0
- package/dist/diff/conditions.js +87 -0
- package/dist/diff/format.js +23 -0
- package/dist/exec/docker.js +158 -0
- package/dist/exec/findings.js +73 -0
- package/dist/exec/plan.js +50 -0
- package/dist/exec/reports.js +90 -0
- package/dist/exec/roots.js +49 -0
- package/dist/exec/run.js +182 -0
- package/dist/exec/steps/build.js +45 -0
- package/dist/exec/steps/coverage-install.js +12 -0
- package/dist/exec/steps/install.js +41 -0
- package/dist/exec/steps/knip.js +28 -0
- package/dist/exec/steps/lint.js +25 -0
- package/dist/exec/steps/lockfiles.js +25 -0
- package/dist/exec/steps/test.js +17 -0
- package/dist/exec/steps/typecheck.js +40 -0
- package/dist/exec/test-reports/coverage-py.js +23 -0
- package/dist/exec/test-reports/coverage-ranges.js +65 -0
- package/dist/exec/test-reports/coverage-summary.js +23 -0
- package/dist/exec/test-reports/jest-json.js +31 -0
- package/dist/exec/test-reports/junit.js +24 -0
- package/dist/exec/test-reports/mocha-json.js +18 -0
- package/dist/exec/test-reports/vitest-json.js +5 -0
- package/dist/exec/test-runners/detect.js +99 -0
- package/dist/exec/test-runners/index.js +6 -0
- package/dist/exec/test-runners/jest.js +27 -0
- package/dist/exec/test-runners/mocha.js +28 -0
- package/dist/exec/test-runners/node-test.js +24 -0
- package/dist/exec/test-runners/pytest.js +18 -0
- package/dist/exec/test-runners/script.js +48 -0
- package/dist/exec/test-runners/spec.js +1 -0
- package/dist/exec/test-runners/vitest.js +28 -0
- package/dist/exec/tests.js +120 -0
- package/dist/exec/types.js +1 -0
- package/dist/manifest/package-json.js +36 -0
- package/dist/manifest/python.js +106 -0
- package/dist/measure/d3/index.js +45 -0
- package/dist/measure/d3/presets.js +100 -0
- package/dist/measure/d3/py/adapter.js +67 -0
- package/dist/measure/d3/py/config.js +80 -0
- package/dist/measure/d3/py/contracts.js +57 -0
- package/dist/measure/d3/py/domain.js +57 -0
- package/dist/measure/d3/py/graph.js +214 -0
- package/dist/measure/d3/py/interpreter.js +31 -0
- package/dist/measure/d3/py/layers.js +120 -0
- package/dist/measure/d3/py/roots.js +58 -0
- package/dist/measure/d3/py/types.js +1 -0
- package/dist/measure/d3/scc.js +64 -0
- package/dist/measure/d3/ts/adapter.js +371 -0
- package/dist/measure/d3/ts/classify.js +107 -0
- package/dist/measure/d3/ts/config.js +318 -0
- package/dist/measure/d3/ts/domain.js +23 -0
- package/dist/measure/d3/ts/rules.js +65 -0
- package/dist/measure/d3/types.js +5 -0
- package/dist/measure/deps/cvss.js +66 -0
- package/dist/measure/deps/declared.js +195 -0
- package/dist/measure/deps/files.js +40 -0
- package/dist/measure/deps/lockfiles.js +308 -0
- package/dist/measure/deps/osv.js +122 -0
- package/dist/measure/deps/registry.js +142 -0
- package/dist/measure/deps/resolved.js +78 -0
- package/dist/measure/deps/units.js +5 -0
- package/dist/measure/eslint/health-plugin.js +19 -0
- package/dist/measure/ops/docs-commands.js +73 -0
- package/dist/measure/ops/docs-resolve.js +217 -0
- package/dist/measure/ops/entry-points.js +49 -0
- package/dist/measure/ops/env-example.js +20 -0
- package/dist/measure/ops/env-reads.js +46 -0
- package/dist/measure/ops/runtime.js +73 -0
- package/dist/measure/p1-1.js +8 -0
- package/dist/measure/p1-2.js +8 -0
- package/dist/measure/p1-3.js +8 -0
- package/dist/measure/p1-4.js +41 -0
- package/dist/measure/p1-5.js +59 -0
- package/dist/measure/p2-1.js +8 -0
- package/dist/measure/p2-2.js +44 -0
- package/dist/measure/p2-3.js +81 -0
- package/dist/measure/p2-4.js +288 -0
- package/dist/measure/p2-5.js +8 -0
- package/dist/measure/p3-1.js +16 -0
- package/dist/measure/p3-2.js +26 -0
- package/dist/measure/p3-3.js +37 -0
- package/dist/measure/p3-4.js +26 -0
- package/dist/measure/p3-5.js +26 -0
- package/dist/measure/p4-1.js +184 -0
- package/dist/measure/p4-2.js +27 -0
- package/dist/measure/p4-3.js +106 -0
- package/dist/measure/p4-4.js +309 -0
- package/dist/measure/p5-1.js +117 -0
- package/dist/measure/p5-2.js +80 -0
- package/dist/measure/p5-3.js +21 -0
- package/dist/measure/p5-4.js +161 -0
- package/dist/measure/p5-5.js +158 -0
- package/dist/measure/p6-1.js +20 -0
- package/dist/measure/p6-2.js +20 -0
- package/dist/measure/p6-3.js +40 -0
- package/dist/measure/p6-4.js +31 -0
- package/dist/measure/p6-5.js +60 -0
- package/dist/measure/p6-6.js +54 -0
- package/dist/measure/py/typecheck-config.js +95 -0
- package/dist/measure/registry.js +31 -0
- package/dist/measure/safety/py-tests.js +124 -0
- package/dist/measure/safety/ts-barrels.js +68 -0
- package/dist/measure/safety/ts-cases.js +111 -0
- package/dist/measure/safety/ts-credits.js +101 -0
- package/dist/measure/safety/ts-imports.js +63 -0
- package/dist/measure/safety/ts-resolve.js +43 -0
- package/dist/measure/ts/directives.js +37 -0
- package/dist/measure/ts/exports.js +60 -0
- package/dist/measure/ts/tsconfig.js +179 -0
- package/dist/measure/types.js +1 -0
- package/dist/measure/units.js +41 -0
- package/dist/methodology/load.js +159 -0
- package/dist/methodology/schema.js +105 -0
- package/dist/python/deadcode.js +27 -0
- package/dist/python/env.js +13 -0
- package/dist/python/helper.js +24 -0
- package/dist/python/imports.js +40 -0
- package/dist/python/nox.js +16 -0
- package/dist/python/scope.js +35 -0
- package/dist/python/tests.js +19 -0
- package/dist/python/typing.js +27 -0
- package/dist/report/escape.js +5 -0
- package/dist/report/format.js +75 -0
- package/dist/report/render.js +213 -0
- package/dist/report/roadmap.js +58 -0
- package/dist/report/scares.js +83 -0
- package/dist/report/style.js +27 -0
- package/dist/report-only/hotspots.js +48 -0
- package/dist/route/check.js +37 -0
- package/dist/route/files.js +74 -0
- package/dist/route/order.js +22 -0
- package/dist/route/select.js +114 -0
- package/dist/route/write.js +13 -0
- package/dist/run/read.js +89 -0
- package/dist/run/recompute.js +19 -0
- package/dist/run/scan.js +196 -0
- package/dist/run/schema.js +1 -0
- package/dist/run/score-run.js +42 -0
- package/dist/run/write.js +4 -0
- package/dist/scope/build.js +173 -0
- package/dist/scope/packages.js +87 -0
- package/dist/scope/rules.js +21 -0
- package/dist/scope/tracked.js +34 -0
- package/dist/scope/ts-lines.js +63 -0
- package/dist/scope/types.js +7 -0
- package/dist/scoring/aggregate.js +38 -0
- package/dist/scoring/pool.js +65 -0
- package/dist/scoring/score.js +81 -0
- package/dist/scoring/what-if.js +38 -0
- package/dist/scripts/checks.js +53 -0
- package/dist/scripts/ci.js +139 -0
- package/dist/scripts/entry-points/find.js +31 -0
- package/dist/scripts/entry-points/index.js +18 -0
- package/dist/scripts/entry-points/justfile.js +62 -0
- package/dist/scripts/entry-points/makefile.js +66 -0
- package/dist/scripts/entry-points/nox.js +29 -0
- package/dist/scripts/entry-points/package-json.js +21 -0
- package/dist/scripts/entry-points/pyproject.js +73 -0
- package/dist/scripts/entry-points/tox.js +130 -0
- package/dist/scripts/expand.js +300 -0
- package/dist/scripts/triggers.js +151 -0
- package/dist/scripts/types.js +1 -0
- package/dist/submit/constants.js +9 -0
- package/dist/submit/payload.js +42 -0
- package/dist/submit/repo-identity.js +46 -0
- package/dist/submit/send.js +29 -0
- package/dist/tools/catalog.js +46 -0
- package/dist/tools/image.js +180 -0
- package/dist/tools/kit.js +71 -0
- package/dist/tools/knip.js +169 -0
- package/dist/tools/outcome.js +21 -0
- package/dist/tools/resolve.js +66 -0
- package/dist/tools/workcopy.js +27 -0
- package/dist/util/ini.js +37 -0
- package/dist/util/pep440.js +163 -0
- package/dist/util/toml.js +27 -0
- package/docker/Dockerfile +142 -0
- package/docker/bin/health-capture +5 -0
- package/docker/bin/health-coverage-install +5 -0
- package/docker/bin/health-eslint +66 -0
- package/docker/bin/health-pytest +10 -0
- package/docker/bin/health-tsc +10 -0
- package/docker/bin/health-venv +11 -0
- package/docker/config/eslint.config.mjs +4 -0
- package/docker/config/eslint.rules.mjs +65 -0
- package/docker/config/eslint.untyped.config.mjs +4 -0
- package/docker/config/knip.json +1 -0
- package/docker/config/ruff.toml +7 -0
- package/methodology/LICENSE +396 -0
- package/methodology/v1.0/METHODOLOGY.md +1176 -0
- package/methodology/v1.0/dimensions/1-safety-net.yaml +240 -0
- package/methodology/v1.0/dimensions/2-type-static-rigor.yaml +289 -0
- package/methodology/v1.0/dimensions/3-architecture-boundaries.yaml +266 -0
- package/methodology/v1.0/dimensions/4-complexity-maintainability.yaml +192 -0
- package/methodology/v1.0/dimensions/5-dependency-security-hygiene.yaml +223 -0
- package/methodology/v1.0/dimensions/6-operability-feedback-loop.yaml +279 -0
- package/methodology/v1.0/methodology.yaml +242 -0
- package/methodology/v1.0/presets/django.yaml +43 -0
- package/methodology/v1.0/presets/express.yaml +47 -0
- package/methodology/v1.0/presets/fastapi.yaml +52 -0
- package/methodology/v1.0/presets/generic.yaml +41 -0
- package/methodology/v1.0/presets/nextjs.yaml +50 -0
- package/methodology/v1.0/scope.yaml +115 -0
- package/package.json +61 -4
- package/python/dead_code_helper.py +265 -0
- package/python/env_helper.py +71 -0
- package/python/graph_helper.py +277 -0
- package/python/imports_helper.py +132 -0
- package/python/nox_helper.py +79 -0
- package/python/scope_helper.py +96 -0
- package/python/tests_helper.py +171 -0
- package/python/typing_helper.py +235 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pythonEnvReads } from '../python/env.js';
|
|
3
|
+
import { readTracked } from '../scope/tracked.js';
|
|
4
|
+
import { dirsUpward } from './ops/entry-points.js';
|
|
5
|
+
import { parseEnvExample } from './ops/env-example.js';
|
|
6
|
+
import { isExcludedEnvName, tsEnvReads } from './ops/env-reads.js';
|
|
7
|
+
import { runtimePinned } from './ops/runtime.js';
|
|
8
|
+
import { logicUnits } from './units.js';
|
|
9
|
+
export const p65 = {
|
|
10
|
+
id: '6.5',
|
|
11
|
+
layer: 'static',
|
|
12
|
+
async measure({ scope, repoPath, methodology }) {
|
|
13
|
+
const lists = methodology.d6;
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const pyFiles = scope.files.filter((f) => f.group === 'python' && f.role === 'logic').map((f) => f.path);
|
|
16
|
+
const pyReads = pyFiles.length === 0 ? {} : pythonEnvReads(repoPath, pyFiles);
|
|
17
|
+
const units = logicUnits(scope).map((unit) => {
|
|
18
|
+
const read = new Set();
|
|
19
|
+
for (const file of unit.files) {
|
|
20
|
+
if (unit.group === 'python') {
|
|
21
|
+
const found = pyReads[file.path];
|
|
22
|
+
if (found?.ok)
|
|
23
|
+
for (const name of found.names)
|
|
24
|
+
read.add(name);
|
|
25
|
+
else
|
|
26
|
+
warnings.push(`6.5: ${file.path}: syntax error, its environment reads are not counted`);
|
|
27
|
+
}
|
|
28
|
+
else
|
|
29
|
+
for (const name of tsEnvReads(scope.getTsSourceFile(file.path)))
|
|
30
|
+
read.add(name);
|
|
31
|
+
}
|
|
32
|
+
const names = [...read].filter((name) => !isExcludedEnvName(name, lists));
|
|
33
|
+
let documented = new Set();
|
|
34
|
+
search: for (const dir of dirsUpward(unit.dir)) {
|
|
35
|
+
for (const name of lists.envExampleFiles) {
|
|
36
|
+
const text = readTracked(repoPath, path.posix.join(dir, name));
|
|
37
|
+
if (text !== null) {
|
|
38
|
+
documented = new Set(Object.keys(parseEnvExample(text)));
|
|
39
|
+
break search;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const numerator = names.filter((name) => documented.has(name)).length;
|
|
44
|
+
const pinned = runtimePinned(repoPath, unit.dir, unit.group, lists.runtimePinFiles, warnings);
|
|
45
|
+
return {
|
|
46
|
+
package: unit.package,
|
|
47
|
+
group: unit.group,
|
|
48
|
+
weight: unit.weight,
|
|
49
|
+
raw: {
|
|
50
|
+
kind: 'components',
|
|
51
|
+
components: {
|
|
52
|
+
runtime_pinned: { kind: 'level', level: pinned ? 'yes' : 'no' },
|
|
53
|
+
env_documented: { kind: 'ratio', numerator, denominator: names.length, scale: 100, ...(names.length === 0 ? { emptyScore: 100 } : {}) },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
return { status: 'measured', units, ...(warnings.length === 0 ? {} : { warnings: [...new Set(warnings)] }) };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readTracked, trackedPaths } from '../scope/tracked.js';
|
|
3
|
+
import { extractCommands } from './ops/docs-commands.js';
|
|
4
|
+
import { DocResolver } from './ops/docs-resolve.js';
|
|
5
|
+
import { logicUnits } from './units.js';
|
|
6
|
+
const AGENT_FILES = ['agents.md', 'claude.md'];
|
|
7
|
+
const README_NAMES = ['readme.md', 'readme', 'readme.rst', 'readme.txt'];
|
|
8
|
+
/** The tracked file of `dir` whose name matches one of `names` case-insensitively, in the order of `names`. */
|
|
9
|
+
function findCi(tracked, dir, names) {
|
|
10
|
+
const inDir = tracked.filter((f) => path.posix.dirname(f) === dir);
|
|
11
|
+
for (const name of names) {
|
|
12
|
+
const found = inDir.find((f) => path.posix.basename(f).toLowerCase() === name);
|
|
13
|
+
if (found)
|
|
14
|
+
return found;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export const p66 = {
|
|
19
|
+
id: '6.6',
|
|
20
|
+
layer: 'static',
|
|
21
|
+
async measure({ scope, repoPath, methodology }) {
|
|
22
|
+
const lists = methodology.d6;
|
|
23
|
+
const tracked = trackedPaths(repoPath);
|
|
24
|
+
const words = {
|
|
25
|
+
commandWords: new Set([
|
|
26
|
+
...lists.docPackageManagers,
|
|
27
|
+
...[...lists.typecheckers, ...lists.linters, ...lists.testRunners].map((t) => (t === 'node --test' ? 'node' : t.replace(/^python3? -m /, ''))),
|
|
28
|
+
]),
|
|
29
|
+
};
|
|
30
|
+
const resolver = new DocResolver(repoPath, lists);
|
|
31
|
+
const commandsOf = (file) => {
|
|
32
|
+
const text = file === undefined ? null : readTracked(repoPath, file);
|
|
33
|
+
return text === null ? [] : extractCommands(text, words);
|
|
34
|
+
};
|
|
35
|
+
const agentFiles = AGENT_FILES.flatMap((name) => findCi(tracked, '.', [name]) ?? []);
|
|
36
|
+
const agent = agentFiles.flatMap((file) => commandsOf(file).map((command) => ({ file, command })));
|
|
37
|
+
const unresolved = agent.filter(({ command }) => !resolver.resolves(command, '.'));
|
|
38
|
+
const warnings = unresolved.slice(0, 5).map(({ file, command }) => `6.6: ${file}:${command.line}: \`${command.text}\` does not resolve`);
|
|
39
|
+
const rootReadme = findCi(tracked, '.', README_NAMES);
|
|
40
|
+
const rootReadmeHasCommands = commandsOf(rootReadme).length > 0;
|
|
41
|
+
const units = logicUnits(scope).map((unit) => {
|
|
42
|
+
let level;
|
|
43
|
+
if (agent.length > 0)
|
|
44
|
+
level = unresolved.length === 0 ? 'agent_file' : 'readme';
|
|
45
|
+
else {
|
|
46
|
+
const own = unit.dir === '.' ? undefined : findCi(tracked, unit.dir, README_NAMES);
|
|
47
|
+
const hasCommands = own !== undefined && commandsOf(own).length > 0 ? true : rootReadmeHasCommands;
|
|
48
|
+
level = hasCommands ? 'readme' : 'none';
|
|
49
|
+
}
|
|
50
|
+
return { package: unit.package, group: unit.group, weight: unit.weight, raw: { kind: 'level', level } };
|
|
51
|
+
});
|
|
52
|
+
return { status: 'measured', units, ...(warnings.length === 0 ? {} : { warnings }) };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { parseIni } from '../../util/ini.js';
|
|
5
|
+
import { readToml } from '../../util/toml.js';
|
|
6
|
+
function asRecord(value) {
|
|
7
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
8
|
+
}
|
|
9
|
+
function truthy(value) {
|
|
10
|
+
if (typeof value === 'boolean')
|
|
11
|
+
return value;
|
|
12
|
+
return typeof value === 'string' && ['true', '1', 'yes', 'on'].includes(value.trim().toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
function pyrightLevel(mode, unsetMeans, basedpyright) {
|
|
15
|
+
if (typeof mode !== 'string')
|
|
16
|
+
return unsetMeans;
|
|
17
|
+
switch (mode) {
|
|
18
|
+
case 'off':
|
|
19
|
+
return 'none';
|
|
20
|
+
case 'basic':
|
|
21
|
+
return 'basic';
|
|
22
|
+
case 'standard':
|
|
23
|
+
return 'standard';
|
|
24
|
+
case 'strict':
|
|
25
|
+
return 'strict';
|
|
26
|
+
case 'recommended':
|
|
27
|
+
case 'all':
|
|
28
|
+
return basedpyright ? 'strict' : unsetMeans;
|
|
29
|
+
default:
|
|
30
|
+
return unsetMeans;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function mypyLevel(section) {
|
|
34
|
+
if (truthy(section.strict))
|
|
35
|
+
return 'strict';
|
|
36
|
+
if (truthy(section.disallow_untyped_defs) && truthy(section.check_untyped_defs))
|
|
37
|
+
return 'standard';
|
|
38
|
+
return 'basic';
|
|
39
|
+
}
|
|
40
|
+
function readText(file) {
|
|
41
|
+
try {
|
|
42
|
+
return readFileSync(file, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** The level one directory declares, or undefined when it holds no pyright or mypy config. pyright wins over mypy. */
|
|
49
|
+
function levelInDirectory(dir, relDir, warnings) {
|
|
50
|
+
const rel = (name) => (relDir === '' ? name : `${relDir}/${name}`);
|
|
51
|
+
const pyproject = existsSync(path.join(dir, 'pyproject.toml')) ? readToml(path.join(dir, 'pyproject.toml')) : undefined;
|
|
52
|
+
if (existsSync(path.join(dir, 'pyproject.toml')) && pyproject === undefined) {
|
|
53
|
+
warnings.push(`2.2: ${rel('pyproject.toml')}: invalid TOML, ignored`);
|
|
54
|
+
}
|
|
55
|
+
const tool = asRecord(pyproject?.tool);
|
|
56
|
+
const pyrightJson = readText(path.join(dir, 'pyrightconfig.json'));
|
|
57
|
+
if (pyrightJson !== undefined) {
|
|
58
|
+
const parsed = ts.parseConfigFileTextToJson('pyrightconfig.json', pyrightJson);
|
|
59
|
+
if (parsed.error)
|
|
60
|
+
warnings.push(`2.2: ${rel('pyrightconfig.json')}: invalid JSON, ignored`);
|
|
61
|
+
else
|
|
62
|
+
return pyrightLevel(asRecord(parsed.config)?.typeCheckingMode, 'standard', false);
|
|
63
|
+
}
|
|
64
|
+
const pyright = asRecord(tool?.pyright);
|
|
65
|
+
if (pyright)
|
|
66
|
+
return pyrightLevel(pyright.typeCheckingMode, 'standard', false);
|
|
67
|
+
const basedpyright = asRecord(tool?.basedpyright);
|
|
68
|
+
if (basedpyright)
|
|
69
|
+
return pyrightLevel(basedpyright.typeCheckingMode, 'strict', true);
|
|
70
|
+
for (const name of ['mypy.ini', '.mypy.ini']) {
|
|
71
|
+
const text = readText(path.join(dir, name));
|
|
72
|
+
const section = text === undefined ? undefined : parseIni(text).mypy;
|
|
73
|
+
if (section)
|
|
74
|
+
return mypyLevel(section);
|
|
75
|
+
}
|
|
76
|
+
const mypyToml = asRecord(tool?.mypy);
|
|
77
|
+
if (mypyToml)
|
|
78
|
+
return mypyLevel(mypyToml);
|
|
79
|
+
const setupCfg = readText(path.join(dir, 'setup.cfg'));
|
|
80
|
+
const cfgSection = setupCfg === undefined ? undefined : parseIni(setupCfg).mypy;
|
|
81
|
+
if (cfgSection)
|
|
82
|
+
return mypyLevel(cfgSection);
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
/** Search from the unit's package directory up to the repository root; the first directory with a config decides. */
|
|
86
|
+
export function pythonLevel(repoPath, packageDir, warnings) {
|
|
87
|
+
const parts = packageDir === '.' ? [] : packageDir.split('/');
|
|
88
|
+
for (let depth = parts.length; depth >= 0; depth -= 1) {
|
|
89
|
+
const relDir = parts.slice(0, depth).join('/');
|
|
90
|
+
const level = levelInDirectory(path.join(repoPath, relDir), relDir, warnings);
|
|
91
|
+
if (level !== undefined)
|
|
92
|
+
return level;
|
|
93
|
+
}
|
|
94
|
+
return 'none';
|
|
95
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { p11 } from './p1-1.js';
|
|
2
|
+
import { p12 } from './p1-2.js';
|
|
3
|
+
import { p13 } from './p1-3.js';
|
|
4
|
+
import { p14 } from './p1-4.js';
|
|
5
|
+
import { p15 } from './p1-5.js';
|
|
6
|
+
import { p21 } from './p2-1.js';
|
|
7
|
+
import { p22 } from './p2-2.js';
|
|
8
|
+
import { p23 } from './p2-3.js';
|
|
9
|
+
import { p24 } from './p2-4.js';
|
|
10
|
+
import { p25 } from './p2-5.js';
|
|
11
|
+
import { p31 } from './p3-1.js';
|
|
12
|
+
import { p32 } from './p3-2.js';
|
|
13
|
+
import { p33 } from './p3-3.js';
|
|
14
|
+
import { p34 } from './p3-4.js';
|
|
15
|
+
import { p35 } from './p3-5.js';
|
|
16
|
+
import { p41 } from './p4-1.js';
|
|
17
|
+
import { p42 } from './p4-2.js';
|
|
18
|
+
import { p43 } from './p4-3.js';
|
|
19
|
+
import { p44 } from './p4-4.js';
|
|
20
|
+
import { p51 } from './p5-1.js';
|
|
21
|
+
import { p52 } from './p5-2.js';
|
|
22
|
+
import { p53 } from './p5-3.js';
|
|
23
|
+
import { p54 } from './p5-4.js';
|
|
24
|
+
import { p55 } from './p5-5.js';
|
|
25
|
+
import { p61 } from './p6-1.js';
|
|
26
|
+
import { p62 } from './p6-2.js';
|
|
27
|
+
import { p63 } from './p6-3.js';
|
|
28
|
+
import { p64 } from './p6-4.js';
|
|
29
|
+
import { p65 } from './p6-5.js';
|
|
30
|
+
import { p66 } from './p6-6.js';
|
|
31
|
+
export const measurers = [p11, p12, p13, p14, p15, p21, p22, p23, p24, p25, p31, p32, p33, p34, p35, p41, p42, p43, p44, p51, p52, p53, p54, p55, p61, p62, p63, p64, p65, p66];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pythonTestFacts } from '../../python/tests.js';
|
|
3
|
+
import { trackedPaths } from '../../scope/tracked.js';
|
|
4
|
+
/** Resolution of Python imports to tracked files, for 1.4. Third-party and stdlib imports resolve to nothing. */
|
|
5
|
+
export class PyImportGraph {
|
|
6
|
+
tracked;
|
|
7
|
+
facts;
|
|
8
|
+
packageOf = new Map();
|
|
9
|
+
constructor(repoPath, files) {
|
|
10
|
+
this.tracked = new Set(trackedPaths(repoPath));
|
|
11
|
+
this.facts = pythonTestFacts(repoPath);
|
|
12
|
+
for (const file of files)
|
|
13
|
+
this.packageOf.set(file.path, file.package);
|
|
14
|
+
}
|
|
15
|
+
factsOf(file) {
|
|
16
|
+
return this.facts[file];
|
|
17
|
+
}
|
|
18
|
+
/** The directories an absolute import is tried from, in order: the importer's own directory, `<P>/src`, `<P>`, `src`, `.`. */
|
|
19
|
+
roots(from) {
|
|
20
|
+
const pkg = this.packageOf.get(from) ?? '.';
|
|
21
|
+
const candidates = [path.posix.dirname(from), pkg === '.' ? 'src' : `${pkg}/src`, pkg, 'src', '.'];
|
|
22
|
+
return [...new Set(candidates)];
|
|
23
|
+
}
|
|
24
|
+
moduleIn(dir, parts) {
|
|
25
|
+
const base = parts.length === 0 ? dir : path.posix.join(dir, ...parts);
|
|
26
|
+
for (const candidate of parts.length === 0 ? [path.posix.join(base, '__init__.py')] : [`${base}.py`, path.posix.join(base, '__init__.py')]) {
|
|
27
|
+
if (this.tracked.has(path.posix.normalize(candidate)))
|
|
28
|
+
return path.posix.normalize(candidate);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/** The file of `module` as imported from `from` (a relative import uses its `level`; an absolute one tries each root, first hit wins). */
|
|
33
|
+
moduleFile(from, level, module) {
|
|
34
|
+
const parts = module === null ? [] : module.split('.');
|
|
35
|
+
if (level > 0) {
|
|
36
|
+
let dir = path.posix.dirname(from);
|
|
37
|
+
for (let i = 1; i < level; i += 1)
|
|
38
|
+
dir = path.posix.dirname(dir);
|
|
39
|
+
return this.moduleIn(dir, parts);
|
|
40
|
+
}
|
|
41
|
+
if (parts.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
for (const root of this.roots(from)) {
|
|
44
|
+
const found = this.moduleIn(root, parts);
|
|
45
|
+
if (found)
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
lookupIn(file, name, seen) {
|
|
51
|
+
const key = `${file}\0${name}`;
|
|
52
|
+
if (seen.has(key))
|
|
53
|
+
return undefined;
|
|
54
|
+
seen.add(key);
|
|
55
|
+
const facts = this.facts[file];
|
|
56
|
+
if (!facts?.ok)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (facts.defined.includes(name))
|
|
59
|
+
return file;
|
|
60
|
+
const binding = facts.reexports.find((r) => r.name === name);
|
|
61
|
+
return binding ? this.fromImport(file, binding.level, binding.module, binding.orig, seen) : undefined;
|
|
62
|
+
}
|
|
63
|
+
/** The file that defines `name` imported by `from <module> import name` in `from`, following `__init__.py` re-exports; a submodule is credited itself. */
|
|
64
|
+
fromImport(from, level, module, name, seen = new Set()) {
|
|
65
|
+
const target = this.moduleFile(from, level, module);
|
|
66
|
+
if (target) {
|
|
67
|
+
const found = this.lookupIn(target, name, seen);
|
|
68
|
+
if (found)
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
const dir = target === undefined ? (module === null && level > 0 ? this.levelDir(from, level) : undefined) : target.endsWith('/__init__.py') || target === '__init__.py' ? path.posix.dirname(target) : undefined;
|
|
72
|
+
if (dir !== undefined) {
|
|
73
|
+
const sub = this.moduleIn(dir, [name]);
|
|
74
|
+
if (sub)
|
|
75
|
+
return sub;
|
|
76
|
+
}
|
|
77
|
+
return target;
|
|
78
|
+
}
|
|
79
|
+
levelDir(from, level) {
|
|
80
|
+
let dir = path.posix.dirname(from);
|
|
81
|
+
for (let i = 1; i < level; i += 1)
|
|
82
|
+
dir = path.posix.dirname(dir);
|
|
83
|
+
return dir;
|
|
84
|
+
}
|
|
85
|
+
/** Every file a star import of `file` reaches: the module itself and the targets of all its re-export bindings. */
|
|
86
|
+
starTargets(file) {
|
|
87
|
+
const facts = this.facts[file];
|
|
88
|
+
const out = [file];
|
|
89
|
+
if (facts?.ok) {
|
|
90
|
+
for (const binding of facts.reexports) {
|
|
91
|
+
const found = this.fromImport(file, binding.level, binding.module, binding.orig);
|
|
92
|
+
if (found !== undefined)
|
|
93
|
+
out.push(found);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/** The files a test file credits through its imports. */
|
|
99
|
+
credits(test) {
|
|
100
|
+
const facts = this.facts[test];
|
|
101
|
+
if (!facts?.ok)
|
|
102
|
+
return [];
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const imp of facts.imports) {
|
|
105
|
+
if (imp.names.length === 0 && !imp.star) {
|
|
106
|
+
const file = this.moduleFile(test, imp.level, imp.module);
|
|
107
|
+
if (file !== undefined)
|
|
108
|
+
out.push(file);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
for (const { name } of imp.names) {
|
|
112
|
+
const found = this.fromImport(test, imp.level, imp.module, name);
|
|
113
|
+
if (found !== undefined)
|
|
114
|
+
out.push(found);
|
|
115
|
+
}
|
|
116
|
+
if (imp.star) {
|
|
117
|
+
const file = this.moduleFile(test, imp.level, imp.module);
|
|
118
|
+
if (file !== undefined)
|
|
119
|
+
out.push(...this.starTargets(file));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
const hasModifier = (node, kind) => ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((m) => m.kind === kind);
|
|
3
|
+
function bindingNames(name, out) {
|
|
4
|
+
if (ts.isIdentifier(name))
|
|
5
|
+
out.push(name.text);
|
|
6
|
+
else
|
|
7
|
+
for (const el of name.elements)
|
|
8
|
+
if (ts.isBindingElement(el))
|
|
9
|
+
bindingNames(el.name, out);
|
|
10
|
+
}
|
|
11
|
+
export function summarize(sf) {
|
|
12
|
+
const summary = { defined: new Set(), named: new Map(), stars: [], namespaces: new Map() };
|
|
13
|
+
const imports = new Map();
|
|
14
|
+
for (const stmt of sf.statements) {
|
|
15
|
+
if (ts.isImportDeclaration(stmt) && ts.isStringLiteralLike(stmt.moduleSpecifier) && stmt.importClause) {
|
|
16
|
+
const specifier = stmt.moduleSpecifier.text;
|
|
17
|
+
const clause = stmt.importClause;
|
|
18
|
+
if (clause.name)
|
|
19
|
+
imports.set(clause.name.text, { specifier, orig: 'default' });
|
|
20
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
21
|
+
for (const el of clause.namedBindings.elements)
|
|
22
|
+
imports.set(el.name.text, { specifier, orig: (el.propertyName ?? el.name).text });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const stmt of sf.statements) {
|
|
27
|
+
if (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt) || ts.isEnumDeclaration(stmt) || ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) {
|
|
28
|
+
if (stmt.name)
|
|
29
|
+
summary.defined.add(stmt.name.text);
|
|
30
|
+
if (hasModifier(stmt, ts.SyntaxKind.DefaultKeyword))
|
|
31
|
+
summary.defined.add('default');
|
|
32
|
+
}
|
|
33
|
+
else if (ts.isVariableStatement(stmt)) {
|
|
34
|
+
const names = [];
|
|
35
|
+
for (const decl of stmt.declarationList.declarations)
|
|
36
|
+
bindingNames(decl.name, names);
|
|
37
|
+
for (const name of names)
|
|
38
|
+
summary.defined.add(name);
|
|
39
|
+
}
|
|
40
|
+
else if (ts.isExportAssignment(stmt)) {
|
|
41
|
+
summary.defined.add('default');
|
|
42
|
+
}
|
|
43
|
+
else if (ts.isExportDeclaration(stmt)) {
|
|
44
|
+
const from = stmt.moduleSpecifier && ts.isStringLiteralLike(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : undefined;
|
|
45
|
+
const clause = stmt.exportClause;
|
|
46
|
+
if (from !== undefined) {
|
|
47
|
+
if (!clause)
|
|
48
|
+
summary.stars.push(from);
|
|
49
|
+
else if (ts.isNamespaceExport(clause))
|
|
50
|
+
summary.namespaces.set(clause.name.text, from);
|
|
51
|
+
else
|
|
52
|
+
for (const el of clause.elements)
|
|
53
|
+
summary.named.set(el.name.text, { specifier: from, orig: (el.propertyName ?? el.name).text });
|
|
54
|
+
}
|
|
55
|
+
else if (clause && ts.isNamedExports(clause)) {
|
|
56
|
+
for (const el of clause.elements) {
|
|
57
|
+
const local = (el.propertyName ?? el.name).text;
|
|
58
|
+
const imported = imports.get(local);
|
|
59
|
+
if (imported)
|
|
60
|
+
summary.named.set(el.name.text, imported);
|
|
61
|
+
else
|
|
62
|
+
summary.defined.add(el.name.text);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return summary;
|
|
68
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
const CASE_ROOTS = new Set(['it', 'test']);
|
|
3
|
+
const CASE_MODIFIERS = new Set(['only', 'concurrent']);
|
|
4
|
+
const EACH_MODIFIERS = new Set(['only', 'concurrent', 'each']);
|
|
5
|
+
/** `it`, `test` or `it.only.each` as a root identifier and the property names after it, or undefined for anything else. */
|
|
6
|
+
function chainOf(expr) {
|
|
7
|
+
const mods = [];
|
|
8
|
+
let current = expr;
|
|
9
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
10
|
+
mods.unshift(current.name.text);
|
|
11
|
+
current = current.expression;
|
|
12
|
+
}
|
|
13
|
+
return ts.isIdentifier(current) ? { root: current.text, mods } : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A test case is a call to `it` or `test`, or to `it`/`test` followed by a chain of `.only` and `.concurrent`, or `.each(...)` (call or
|
|
17
|
+
* tagged template) and then the call itself. `.skip`, `.todo`, `.fails`, `.skipIf` and `.runIf` are not counted; `describe` is not a case.
|
|
18
|
+
*/
|
|
19
|
+
function isCaseCall(call) {
|
|
20
|
+
const callee = call.expression;
|
|
21
|
+
const direct = chainOf(callee);
|
|
22
|
+
if (direct)
|
|
23
|
+
return CASE_ROOTS.has(direct.root) && direct.mods.every((m) => CASE_MODIFIERS.has(m));
|
|
24
|
+
const each = ts.isCallExpression(callee) ? callee.expression : ts.isTaggedTemplateExpression(callee) ? callee.tag : undefined;
|
|
25
|
+
const chain = each ? chainOf(each) : undefined;
|
|
26
|
+
return chain !== undefined && CASE_ROOTS.has(chain.root) && chain.mods.at(-1) === 'each' && chain.mods.every((m) => EACH_MODIFIERS.has(m));
|
|
27
|
+
}
|
|
28
|
+
const isFunctionLike = (node) => ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
29
|
+
function isAssertionCall(call, testParam) {
|
|
30
|
+
const callee = call.expression;
|
|
31
|
+
if (ts.isIdentifier(callee))
|
|
32
|
+
return ['expect', 'assert', 'expectTypeOf'].includes(callee.text);
|
|
33
|
+
if (!ts.isPropertyAccessExpression(callee))
|
|
34
|
+
return false;
|
|
35
|
+
const props = [];
|
|
36
|
+
let root = callee;
|
|
37
|
+
while (ts.isPropertyAccessExpression(root)) {
|
|
38
|
+
props.unshift(root.name.text);
|
|
39
|
+
root = root.expression;
|
|
40
|
+
}
|
|
41
|
+
if (!ts.isIdentifier(root))
|
|
42
|
+
return false;
|
|
43
|
+
if (root.text === 'expect')
|
|
44
|
+
return props.length === 1 && (props[0] === 'soft' || props[0] === 'poll');
|
|
45
|
+
if (root.text === 'assert')
|
|
46
|
+
return true;
|
|
47
|
+
if (testParam !== undefined && root.text === testParam) {
|
|
48
|
+
const [first] = props;
|
|
49
|
+
return first !== undefined && (first.startsWith('assert') || (props.length === 1 && (first === 'is' || first === 'deepEqual')));
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
function containsAssertion(node, testParam, calls) {
|
|
54
|
+
let found = false;
|
|
55
|
+
const visit = (child) => {
|
|
56
|
+
if (found && calls === undefined)
|
|
57
|
+
return;
|
|
58
|
+
if (ts.isCallExpression(child)) {
|
|
59
|
+
if (isAssertionCall(child, testParam))
|
|
60
|
+
found = true;
|
|
61
|
+
calls?.push(child);
|
|
62
|
+
}
|
|
63
|
+
ts.forEachChild(child, visit);
|
|
64
|
+
};
|
|
65
|
+
visit(node);
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
68
|
+
/** Functions declared in the file by name: function declarations and `const x = <arrow or function expression>`. */
|
|
69
|
+
function helpersOf(sf) {
|
|
70
|
+
const helpers = new Map();
|
|
71
|
+
const visit = (node) => {
|
|
72
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.body)
|
|
73
|
+
helpers.set(node.name.text, node.body);
|
|
74
|
+
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isFunctionLike(node.initializer))
|
|
75
|
+
helpers.set(node.name.text, node.initializer.body);
|
|
76
|
+
ts.forEachChild(node, visit);
|
|
77
|
+
};
|
|
78
|
+
visit(sf);
|
|
79
|
+
return helpers;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The test cases of a file. Assertions: `expect(...)`, `expect.soft`, `expect.poll`, `assert(...)`, `assert.*`, `expectTypeOf(...)` and
|
|
83
|
+
* `t.assert*`, `t.is`, `t.deepEqual` where `t` is the callback's first parameter, searched in the whole body. A call to a helper declared in
|
|
84
|
+
* the same file counts when the helper's body directly contains an assertion (one level). A case with no function argument has no assertion.
|
|
85
|
+
*/
|
|
86
|
+
export function testCases(sf) {
|
|
87
|
+
const helpers = helpersOf(sf);
|
|
88
|
+
const cases = [];
|
|
89
|
+
const visit = (node) => {
|
|
90
|
+
if (ts.isCallExpression(node) && isCaseCall(node)) {
|
|
91
|
+
const callback = [...node.arguments].reverse().find(isFunctionLike);
|
|
92
|
+
let asserts = false;
|
|
93
|
+
if (callback) {
|
|
94
|
+
const first = callback.parameters[0];
|
|
95
|
+
const testParam = first && ts.isIdentifier(first.name) ? first.name.text : undefined;
|
|
96
|
+
const calls = [];
|
|
97
|
+
asserts = containsAssertion(callback.body, testParam, calls);
|
|
98
|
+
if (!asserts) {
|
|
99
|
+
asserts = calls.some((call) => {
|
|
100
|
+
const helper = ts.isIdentifier(call.expression) ? helpers.get(call.expression.text) : undefined;
|
|
101
|
+
return helper !== undefined && containsAssertion(helper, undefined);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
cases.push({ line: sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1, asserts });
|
|
106
|
+
}
|
|
107
|
+
ts.forEachChild(node, visit);
|
|
108
|
+
};
|
|
109
|
+
visit(sf);
|
|
110
|
+
return cases;
|
|
111
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readTracked } from '../../scope/tracked.js';
|
|
2
|
+
import { parseTsFile } from '../../scope/ts-lines.js';
|
|
3
|
+
import { summarize } from './ts-barrels.js';
|
|
4
|
+
import { importFacts, namespaceAccesses } from './ts-imports.js';
|
|
5
|
+
/** Logic files of the TS/JS group credited by the imports of one test file, with barrels followed to the defining file. */
|
|
6
|
+
export class TsCredits {
|
|
7
|
+
repoPath;
|
|
8
|
+
scope;
|
|
9
|
+
resolver;
|
|
10
|
+
summaries = new Map();
|
|
11
|
+
parsed = new Map();
|
|
12
|
+
constructor(repoPath, scope, resolver) {
|
|
13
|
+
this.repoPath = repoPath;
|
|
14
|
+
this.scope = scope;
|
|
15
|
+
this.resolver = resolver;
|
|
16
|
+
}
|
|
17
|
+
/** A scoped file from the scope's cache; a barrel or other out-of-scope tracked file is parsed from its text. */
|
|
18
|
+
source(file) {
|
|
19
|
+
const cached = this.parsed.get(file);
|
|
20
|
+
if (cached !== undefined)
|
|
21
|
+
return cached ?? undefined;
|
|
22
|
+
let sf = null;
|
|
23
|
+
try {
|
|
24
|
+
sf = this.scope.getTsSourceFile(file);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
const text = readTracked(this.repoPath, file);
|
|
28
|
+
sf = text === null ? null : parseTsFile(file, text);
|
|
29
|
+
}
|
|
30
|
+
this.parsed.set(file, sf);
|
|
31
|
+
return sf ?? undefined;
|
|
32
|
+
}
|
|
33
|
+
summary(file) {
|
|
34
|
+
const cached = this.summaries.get(file);
|
|
35
|
+
if (cached !== undefined)
|
|
36
|
+
return cached ?? undefined;
|
|
37
|
+
const sf = this.source(file);
|
|
38
|
+
const summary = sf ? summarize(sf) : null;
|
|
39
|
+
this.summaries.set(file, summary);
|
|
40
|
+
return summary ?? undefined;
|
|
41
|
+
}
|
|
42
|
+
/** The file defining `name` as exported by `start`: breadth first through `export *` sources, first definition wins. */
|
|
43
|
+
define(start, name, seen) {
|
|
44
|
+
const queue = [start];
|
|
45
|
+
while (queue.length > 0) {
|
|
46
|
+
const file = queue.shift();
|
|
47
|
+
const key = `${file}\0${name}`;
|
|
48
|
+
if (seen.has(key))
|
|
49
|
+
continue;
|
|
50
|
+
seen.add(key);
|
|
51
|
+
const summary = this.summary(file);
|
|
52
|
+
if (!summary)
|
|
53
|
+
continue;
|
|
54
|
+
if (summary.defined.has(name))
|
|
55
|
+
return file;
|
|
56
|
+
const named = summary.named.get(name);
|
|
57
|
+
if (named) {
|
|
58
|
+
const target = this.resolver.resolve(named.specifier, file);
|
|
59
|
+
if (target !== undefined)
|
|
60
|
+
return this.define(target, named.orig, seen) ?? target;
|
|
61
|
+
}
|
|
62
|
+
const namespace = summary.namespaces.get(name);
|
|
63
|
+
if (namespace !== undefined) {
|
|
64
|
+
const target = this.resolver.resolve(namespace, file);
|
|
65
|
+
if (target !== undefined)
|
|
66
|
+
return target;
|
|
67
|
+
}
|
|
68
|
+
for (const star of summary.stars) {
|
|
69
|
+
const target = this.resolver.resolve(star, file);
|
|
70
|
+
if (target !== undefined)
|
|
71
|
+
queue.push(target);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
/** Every file the test file credits (callers keep only logic-role files). */
|
|
77
|
+
credits(test) {
|
|
78
|
+
const out = new Set();
|
|
79
|
+
const sf = this.source(test);
|
|
80
|
+
if (!sf)
|
|
81
|
+
return out;
|
|
82
|
+
const facts = importFacts(sf);
|
|
83
|
+
const namespaces = namespaceAccesses(sf, new Set(facts.flatMap((f) => (f.local === undefined ? [] : [f.local]))));
|
|
84
|
+
for (const fact of facts) {
|
|
85
|
+
const target = this.resolver.resolve(fact.specifier, test);
|
|
86
|
+
if (target === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
if (fact.name === undefined || fact.name === '*') {
|
|
89
|
+
out.add(target);
|
|
90
|
+
for (const name of fact.local === undefined ? [] : [...(namespaces.get(fact.local) ?? [])]) {
|
|
91
|
+
const defining = this.define(target, name, new Set());
|
|
92
|
+
if (defining !== undefined)
|
|
93
|
+
out.add(defining);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
out.add(this.define(target, fact.name, new Set()) ?? target);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
}
|