@thomasminh1995/depverdict 0.6.0-alpha.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/LICENSE +21 -0
- package/README.md +518 -0
- package/bin/depverdict.js +7 -0
- package/bin/upgradelens.js +7 -0
- package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
- package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
- package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
- package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
- package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
- package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
- package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
- package/docs/IA-04-CLI-Orchestration.md +235 -0
- package/docs/IA-05-Real-Provider-Validation.md +235 -0
- package/docs/IA-05-VinGrade-Validation.md +339 -0
- package/docs/MVP-01.md +48 -0
- package/docs/MVP-02-Architecture.md +536 -0
- package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
- package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
- package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
- package/docs/MVP-02-Knowledge-Manifest.md +279 -0
- package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
- package/docs/MVP-02-Knowledge-Store.md +87 -0
- package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
- package/docs/MVP-02-Research-Planning.md +196 -0
- package/docs/MVP-02-Source-Provenance.md +81 -0
- package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
- package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
- package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
- package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
- package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
- package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
- package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
- package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
- package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
- package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
- package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
- package/docs/VinGrade-MVP-02-Validation.md +480 -0
- package/docs/VinGrade-RC02-Live-Validation.md +273 -0
- package/docs/ai-capability-discovery.md +573 -0
- package/docs/ai-engineering-production-readiness.md +390 -0
- package/docs/ai-engineering-review.md +251 -0
- package/docs/ai-runtime-governance-discovery.md +754 -0
- package/docs/architecture-overview.md +78 -0
- package/docs/cli-progress.md +111 -0
- package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
- package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
- package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
- package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
- package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
- package/docs/gateway-runtime-discovery.md +614 -0
- package/docs/live-ai-validation.md +299 -0
- package/docs/migration-planning-qualification-resolution.md +99 -0
- package/docs/migrations/upgradelens-to-depverdict.md +97 -0
- package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
- package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
- package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
- package/docs/mvp-05-deterministic-context-runtime.md +127 -0
- package/docs/mvp-05-migration-checklist-contract.md +166 -0
- package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
- package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
- package/docs/mvp-05-provider-neutral-generator.md +79 -0
- package/docs/ollama-local-smoke-validation.md +156 -0
- package/docs/openai-compatible-runtime-discovery.md +789 -0
- package/docs/openrouter-one-dependency-validation.md +205 -0
- package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
- package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
- package/docs/package-content-policy.md +98 -0
- package/docs/releases/v0.5.0-technical-preview.md +114 -0
- package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
- package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
- package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
- package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
- package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
- package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
- package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
- package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
- package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
- package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
- package/docs/reviews/oss-03-community-scaffolding.md +378 -0
- package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
- package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
- package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
- package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
- package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
- package/docs/runtime-contract-discovery.md +521 -0
- package/docs/structured-output-compatibility-report.md +100 -0
- package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
- package/docs/version-analysis-architecture.md +827 -0
- package/eval/README.md +86 -0
- package/eval/datasets/generic/declared-constraint.json +59 -0
- package/eval/datasets/generic/evidence-conflict.json +73 -0
- package/eval/datasets/generic/major-breaking-release.json +66 -0
- package/eval/datasets/generic/missing-evidence.json +46 -0
- package/eval/datasets/generic/patch-release-low.json +59 -0
- package/eval/datasets/node/axios-patch-low.json +59 -0
- package/eval/datasets/node/react-major-breaking.json +66 -0
- package/eval/datasets/node/react-minor-compatibility.json +66 -0
- package/eval/datasets/python/fastapi-deprecation.json +66 -0
- package/eval/datasets/python/pydantic-major-breaking.json +66 -0
- package/eval/migration-planning/golden-dataset-v2.json +306 -0
- package/eval/migration-planning/golden-dataset.json +214 -0
- package/eval/schemas/expected-result.schema.json +88 -0
- package/eval/schemas/golden-case.schema.json +181 -0
- package/package.json +57 -0
- package/schemas/ai-scorecard.schema.json +132 -0
- package/schemas/benchmark-report.schema.json +189 -0
- package/schemas/benchmark.schema.json +55 -0
- package/schemas/capability-profile.schema.json +44 -0
- package/schemas/conformance-report.schema.json +150 -0
- package/schemas/deployment-profile.schema.json +64 -0
- package/schemas/evaluation-report.schema.json +158 -0
- package/schemas/knowledge-evidence-bundle.schema.json +150 -0
- package/schemas/knowledge-manifest.schema.json +548 -0
- package/schemas/metrics.schema.json +178 -0
- package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
- package/schemas/migration-checklist.schema.json +706 -0
- package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
- package/schemas/migration-evaluation-dataset.schema.json +204 -0
- package/schemas/migration-planning-qualification-record.schema.json +234 -0
- package/schemas/project-manifest.schema.json +308 -0
- package/schemas/qualification-record.schema.json +56 -0
- package/schemas/repository-impact-evidence.schema.json +232 -0
- package/schemas/repository-impact.schema.json +202 -0
- package/schemas/upgrade-decision.schema.json +273 -0
- package/schemas/usage-index.schema.json +179 -0
- package/schemas/version-analysis.schema.json +449 -0
- package/src/ai-runtime-debug.js +325 -0
- package/src/ai-runtime-error.js +42 -0
- package/src/ai-runtime.js +174 -0
- package/src/ai-scorecard.js +204 -0
- package/src/ai-version-analysis.js +484 -0
- package/src/artifact-root-compatibility.js +91 -0
- package/src/benchmark-report.js +111 -0
- package/src/benchmark-runner.js +191 -0
- package/src/canonical-json.js +69 -0
- package/src/cli.js +1299 -0
- package/src/conformance-report.js +158 -0
- package/src/conformance-runner.js +253 -0
- package/src/constants.js +73 -0
- package/src/cooperative-scheduler.js +79 -0
- package/src/dependencies.js +44 -0
- package/src/dependency-ai-context.js +625 -0
- package/src/detectors.js +253 -0
- package/src/discovery.js +234 -0
- package/src/ecosystem-version-adapter.js +294 -0
- package/src/environment-compatibility.js +77 -0
- package/src/evaluation-comparator.js +158 -0
- package/src/evaluation-report.js +76 -0
- package/src/evaluation-runner.js +248 -0
- package/src/evidence-source-adapter.js +472 -0
- package/src/files.js +89 -0
- package/src/governance-diagnostics.js +64 -0
- package/src/governance-loader.js +63 -0
- package/src/governance-metadata.js +346 -0
- package/src/governance-validator.js +360 -0
- package/src/http/bounded-fetch.js +278 -0
- package/src/http/cli-http-runtime.js +44 -0
- package/src/impact/input-loader.js +157 -0
- package/src/impact/matcher.js +40 -0
- package/src/impact/repository-impact.js +199 -0
- package/src/impact/runtime.js +24 -0
- package/src/impact/status.js +62 -0
- package/src/impact/writer.js +30 -0
- package/src/impact-evidence/input-loader.js +202 -0
- package/src/impact-evidence/repository-impact-evidence.js +234 -0
- package/src/impact-evidence/runtime.js +16 -0
- package/src/impact-evidence/writer.js +30 -0
- package/src/index.js +563 -0
- package/src/installed-version-baseline.js +196 -0
- package/src/knowledge-cache.js +324 -0
- package/src/knowledge-evidence-bundle.js +101 -0
- package/src/knowledge-evidence-producer.js +233 -0
- package/src/knowledge-manifest-builder.js +188 -0
- package/src/knowledge-manifest-writer.js +32 -0
- package/src/knowledge-manifest.js +255 -0
- package/src/knowledge-research.js +615 -0
- package/src/metrics-engine.js +205 -0
- package/src/migration-checklist/ai-candidate.js +320 -0
- package/src/migration-checklist/assembler.js +37 -0
- package/src/migration-checklist/context-runtime.js +828 -0
- package/src/migration-checklist/evaluation/action-criteria.js +244 -0
- package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
- package/src/migration-checklist/evaluation/comparator.js +279 -0
- package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
- package/src/migration-checklist/evaluation/dataset.js +336 -0
- package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
- package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
- package/src/migration-checklist/evaluation/metrics.js +158 -0
- package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
- package/src/migration-checklist/evaluation/qualification.js +239 -0
- package/src/migration-checklist/evaluation/runner-v2.js +294 -0
- package/src/migration-checklist/evaluation/runner.js +194 -0
- package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
- package/src/migration-checklist/evaluation/scorecard.js +86 -0
- package/src/migration-checklist/extractive-candidate.js +166 -0
- package/src/migration-checklist/extractive-prompt.js +62 -0
- package/src/migration-checklist/generator.js +702 -0
- package/src/migration-checklist/grounding-policy.js +117 -0
- package/src/migration-checklist/input-loader.js +613 -0
- package/src/migration-checklist/migration-checklist.js +635 -0
- package/src/migration-checklist/presentation.js +292 -0
- package/src/migration-checklist/progress.js +96 -0
- package/src/migration-checklist/prompt.js +83 -0
- package/src/migration-checklist/qualification-guard.js +462 -0
- package/src/migration-checklist/qualification-resolution.js +122 -0
- package/src/migration-checklist/qualification-store.js +225 -0
- package/src/migration-checklist/runtime.js +205 -0
- package/src/migration-checklist/verification.js +134 -0
- package/src/migration-checklist/writer.js +35 -0
- package/src/openai-compatible-provider.js +451 -0
- package/src/orchestration/failure-log.js +32 -0
- package/src/orchestration/pipeline.js +200 -0
- package/src/orchestration/progress-events.js +337 -0
- package/src/orchestration/progress-reporter.js +131 -0
- package/src/orchestration/text-writer.js +22 -0
- package/src/portable.js +13 -0
- package/src/product-completion.js +249 -0
- package/src/project-manifest-input.js +90 -0
- package/src/project-manifest.js +141 -0
- package/src/python-requirements.js +137 -0
- package/src/registry/npm-packument.js +256 -0
- package/src/registry/npm-registry-adapter.js +262 -0
- package/src/registry/pypi-project.js +300 -0
- package/src/registry/pypi-registry-adapter.js +235 -0
- package/src/registry/sanitize-registry-body.js +51 -0
- package/src/renderers/console.js +160 -0
- package/src/renderers/impact-presentation.js +278 -0
- package/src/renderers/markdown.js +172 -0
- package/src/research-plan.js +455 -0
- package/src/runtime-conformance.js +275 -0
- package/src/source-provenance.js +393 -0
- package/src/source-url.js +62 -0
- package/src/structured-output-schema.js +66 -0
- package/src/target-selector.js +306 -0
- package/src/upgrade-decision/input-loader.js +107 -0
- package/src/upgrade-decision/presentation.js +43 -0
- package/src/upgrade-decision/runtime.js +21 -0
- package/src/upgrade-decision/upgrade-decision.js +626 -0
- package/src/upgrade-decision/writer.js +30 -0
- package/src/usage/analyzer-registry.js +63 -0
- package/src/usage/coverage.js +116 -0
- package/src/usage/input-loader.js +139 -0
- package/src/usage/js/analyzer.js +240 -0
- package/src/usage/js/parser.js +21 -0
- package/src/usage/runtime.js +187 -0
- package/src/usage/scope.js +44 -0
- package/src/usage/source-files.js +50 -0
- package/src/usage/usage-index.js +217 -0
- package/src/usage/writer.js +31 -0
- package/src/version-analysis-loader.js +203 -0
- package/src/version-analysis-manifest.js +314 -0
- package/src/version-analysis-writer.js +30 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
function normalizeExtension(extension) {
|
|
4
|
+
if (typeof extension !== 'string' || !/^\.[a-z0-9]+$/i.test(extension)) {
|
|
5
|
+
throw new Error(`Usage analyzer extension must start with a dot: ${extension}.`);
|
|
6
|
+
}
|
|
7
|
+
return extension.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function validateAnalyzer(analyzer) {
|
|
11
|
+
if (!analyzer || typeof analyzer !== 'object') throw new Error('Usage analyzer must be an object.');
|
|
12
|
+
if (typeof analyzer.id !== 'string' || analyzer.id.length === 0) {
|
|
13
|
+
throw new Error('Usage analyzer id must be a non-empty string.');
|
|
14
|
+
}
|
|
15
|
+
if (typeof analyzer.version !== 'string' || analyzer.version.length === 0) {
|
|
16
|
+
throw new Error(`Usage analyzer ${analyzer.id} version must be a non-empty string.`);
|
|
17
|
+
}
|
|
18
|
+
if (!Array.isArray(analyzer.ecosystems) || analyzer.ecosystems.length === 0) {
|
|
19
|
+
throw new Error(`Usage analyzer ${analyzer.id} must declare at least one ecosystem.`);
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(analyzer.extensions) || analyzer.extensions.length === 0) {
|
|
22
|
+
throw new Error(`Usage analyzer ${analyzer.id} must declare at least one extension.`);
|
|
23
|
+
}
|
|
24
|
+
if (typeof analyzer.analyze !== 'function') {
|
|
25
|
+
throw new Error(`Usage analyzer ${analyzer.id} must expose analyze().`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createUsageAnalyzerRegistry(analyzers = []) {
|
|
30
|
+
const byId = new Map();
|
|
31
|
+
const byCapability = new Map();
|
|
32
|
+
|
|
33
|
+
for (const analyzer of analyzers) {
|
|
34
|
+
validateAnalyzer(analyzer);
|
|
35
|
+
if (byId.has(analyzer.id)) throw new Error(`Duplicate usage analyzer id: ${analyzer.id}.`);
|
|
36
|
+
byId.set(analyzer.id, analyzer);
|
|
37
|
+
|
|
38
|
+
for (const ecosystem of analyzer.ecosystems) {
|
|
39
|
+
for (const declaredExtension of analyzer.extensions) {
|
|
40
|
+
const extension = normalizeExtension(declaredExtension);
|
|
41
|
+
const key = `${ecosystem}\0${extension}`;
|
|
42
|
+
if (byCapability.has(key)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Usage analyzer capability conflict for ${ecosystem}/${extension}: `
|
|
45
|
+
+ `${byCapability.get(key).id} and ${analyzer.id}.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
byCapability.set(key, analyzer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
analyzers: () => [...byId.values()],
|
|
55
|
+
extensions: () => [...new Set([...byCapability.keys()].map((key) => key.split('\0')[1]))].sort(),
|
|
56
|
+
forEcosystem(ecosystem) {
|
|
57
|
+
return [...byId.values()].filter((analyzer) => analyzer.ecosystems.includes(ecosystem));
|
|
58
|
+
},
|
|
59
|
+
find(ecosystem, filePath) {
|
|
60
|
+
return byCapability.get(`${ecosystem}\0${path.extname(filePath).toLowerCase()}`) ?? null;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { compareText } from '../portable.js';
|
|
2
|
+
|
|
3
|
+
export const USAGE_COVERAGE_STATUSES = Object.freeze([
|
|
4
|
+
'complete',
|
|
5
|
+
'partial',
|
|
6
|
+
'unavailable',
|
|
7
|
+
'failed'
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const USAGE_COVERAGE_REASON_CODES = Object.freeze([
|
|
11
|
+
'COVERAGE_COMPLETE',
|
|
12
|
+
'ANALYZER_UNAVAILABLE',
|
|
13
|
+
'ANALYZER_AMBIGUOUS',
|
|
14
|
+
'SOURCE_SCAN_INCOMPLETE',
|
|
15
|
+
'SOURCE_PARSE_FAILED',
|
|
16
|
+
'FILE_UNREADABLE',
|
|
17
|
+
'ANALYZER_FAILED',
|
|
18
|
+
'MULTIPLE_ANALYSIS_FAILURES',
|
|
19
|
+
'COVERAGE_METADATA_MISSING'
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const compareCoverage = (left, right) => (
|
|
23
|
+
compareText(left.projectId, right.projectId)
|
|
24
|
+
|| compareText(left.ecosystem, right.ecosystem)
|
|
25
|
+
|| compareText(left.projectPath, right.projectPath)
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function failureReason(counts) {
|
|
29
|
+
const failures = [
|
|
30
|
+
counts.scanFailureCount > 0 ? 'SOURCE_SCAN_INCOMPLETE' : null,
|
|
31
|
+
counts.parseFailureCount > 0 ? 'SOURCE_PARSE_FAILED' : null,
|
|
32
|
+
counts.unreadableFileCount > 0 ? 'FILE_UNREADABLE' : null,
|
|
33
|
+
counts.analyzerFailureCount > 0 ? 'ANALYZER_FAILED' : null
|
|
34
|
+
].filter(Boolean);
|
|
35
|
+
return failures.length === 1 ? failures[0] : 'MULTIPLE_ANALYSIS_FAILURES';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildProjectUsageCoverage({
|
|
39
|
+
projectId,
|
|
40
|
+
projectPath,
|
|
41
|
+
ecosystem,
|
|
42
|
+
analyzers,
|
|
43
|
+
scannedFileCount = 0,
|
|
44
|
+
analyzedFileCount = 0,
|
|
45
|
+
parseFailureCount = 0,
|
|
46
|
+
analyzerFailureCount = 0,
|
|
47
|
+
unreadableFileCount = 0,
|
|
48
|
+
scanFailureCount = 0
|
|
49
|
+
}) {
|
|
50
|
+
const candidates = [...new Map(
|
|
51
|
+
analyzers.map((analyzer) => [`${analyzer.id}\0${analyzer.version}`, analyzer])
|
|
52
|
+
).values()];
|
|
53
|
+
const counts = {
|
|
54
|
+
scannedFileCount,
|
|
55
|
+
analyzedFileCount,
|
|
56
|
+
parseFailureCount,
|
|
57
|
+
analyzerFailureCount,
|
|
58
|
+
unreadableFileCount,
|
|
59
|
+
scanFailureCount
|
|
60
|
+
};
|
|
61
|
+
let status;
|
|
62
|
+
let reasonCode;
|
|
63
|
+
let analyzer = null;
|
|
64
|
+
if (candidates.length === 0) {
|
|
65
|
+
status = 'unavailable';
|
|
66
|
+
reasonCode = 'ANALYZER_UNAVAILABLE';
|
|
67
|
+
} else if (candidates.length > 1) {
|
|
68
|
+
status = 'unavailable';
|
|
69
|
+
reasonCode = 'ANALYZER_AMBIGUOUS';
|
|
70
|
+
} else {
|
|
71
|
+
analyzer = { id: candidates[0].id, version: candidates[0].version };
|
|
72
|
+
const failureCount = parseFailureCount + analyzerFailureCount
|
|
73
|
+
+ unreadableFileCount + scanFailureCount;
|
|
74
|
+
if (failureCount === 0) {
|
|
75
|
+
status = 'complete';
|
|
76
|
+
reasonCode = 'COVERAGE_COMPLETE';
|
|
77
|
+
} else if (analyzerFailureCount > 0 && analyzedFileCount === 0 && parseFailureCount === 0) {
|
|
78
|
+
status = 'failed';
|
|
79
|
+
reasonCode = failureReason(counts);
|
|
80
|
+
} else {
|
|
81
|
+
status = 'partial';
|
|
82
|
+
reasonCode = failureReason(counts);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
projectId,
|
|
87
|
+
projectPath,
|
|
88
|
+
ecosystem,
|
|
89
|
+
status,
|
|
90
|
+
analyzer,
|
|
91
|
+
...counts,
|
|
92
|
+
reasonCode
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function unavailableLegacyCoverage(projectId, ecosystem = null) {
|
|
97
|
+
return {
|
|
98
|
+
projectId,
|
|
99
|
+
projectPath: null,
|
|
100
|
+
ecosystem,
|
|
101
|
+
status: 'unavailable',
|
|
102
|
+
analyzer: null,
|
|
103
|
+
scannedFileCount: 0,
|
|
104
|
+
analyzedFileCount: 0,
|
|
105
|
+
parseFailureCount: 0,
|
|
106
|
+
analyzerFailureCount: 0,
|
|
107
|
+
unreadableFileCount: 0,
|
|
108
|
+
scanFailureCount: 0,
|
|
109
|
+
reasonCode: 'COVERAGE_METADATA_MISSING'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function coverageForProject(usageIndex, projectId, ecosystem = null) {
|
|
114
|
+
return usageIndex?.analysis?.coverage?.find((item) => item.projectId === projectId)
|
|
115
|
+
?? unavailableLegacyCoverage(projectId, ecosystem);
|
|
116
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_MANIFEST_PATH,
|
|
6
|
+
DEFAULT_VERSION_ANALYSIS_PATH,
|
|
7
|
+
VERSION_ANALYSIS_SCHEMA_VERSION
|
|
8
|
+
} from '../constants.js';
|
|
9
|
+
import { isPortableRelativePath } from '../portable.js';
|
|
10
|
+
import { loadProjectManifestInput } from '../project-manifest-input.js';
|
|
11
|
+
import { validateVersionAnalysisManifest } from '../version-analysis-manifest.js';
|
|
12
|
+
|
|
13
|
+
export class UsageDiscoveryInputError extends Error {
|
|
14
|
+
constructor(message, code = 'USAGE_DISCOVERY_INPUT_INVALID') {
|
|
15
|
+
super(`Usage Discovery input error: ${message}`);
|
|
16
|
+
this.name = 'UsageDiscoveryInputError';
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function digest(bytes) {
|
|
22
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function readBytes(source, label) {
|
|
26
|
+
if (typeof source === 'string' || source instanceof URL) return readFile(source);
|
|
27
|
+
if (source && typeof source === 'object' && 'bytes' in source && source.bytes instanceof Uint8Array) {
|
|
28
|
+
return Buffer.from(source.bytes);
|
|
29
|
+
}
|
|
30
|
+
throw new UsageDiscoveryInputError(`${label} source must be a file path or { bytes, artifact }.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function artifactFor(source, configured, defaultArtifact, label) {
|
|
34
|
+
const artifact = source && typeof source === 'object' && 'bytes' in source
|
|
35
|
+
? source.artifact
|
|
36
|
+
: configured ?? defaultArtifact;
|
|
37
|
+
if (!isPortableRelativePath(artifact)) {
|
|
38
|
+
throw new UsageDiscoveryInputError(`${label} artifact must be a portable repository-relative path.`);
|
|
39
|
+
}
|
|
40
|
+
return artifact;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateLineage(projectInput, versionAnalysis) {
|
|
44
|
+
const expected = versionAnalysis.input.projectManifest;
|
|
45
|
+
const actual = projectInput.input.projectManifest;
|
|
46
|
+
const mismatches = [];
|
|
47
|
+
if (expected.schemaVersion !== actual.schemaVersion) mismatches.push('schemaVersion');
|
|
48
|
+
if (expected.artifactDigest !== actual.artifactDigest) mismatches.push('artifactDigest');
|
|
49
|
+
if (expected.repository.name !== actual.repository.name) mismatches.push('repository.name');
|
|
50
|
+
if (expected.repository.root !== actual.repository.root) mismatches.push('repository.root');
|
|
51
|
+
if (mismatches.length > 0) {
|
|
52
|
+
throw new UsageDiscoveryInputError(
|
|
53
|
+
`Version Analysis Project Manifest lineage mismatch: ${mismatches.join(', ')}.`,
|
|
54
|
+
'LINEAGE_MISMATCH'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateDependencyReferences(projectManifest, versionAnalysis) {
|
|
60
|
+
const projects = new Map(projectManifest.projects.map((project) => [project.id, project]));
|
|
61
|
+
const errors = [];
|
|
62
|
+
for (const result of versionAnalysis.results) {
|
|
63
|
+
const occurrence = result.dependency;
|
|
64
|
+
const project = projects.get(occurrence.projectId);
|
|
65
|
+
if (!project) {
|
|
66
|
+
errors.push(`${occurrence.packageId} references unknown project ${occurrence.projectId}`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const declared = project.dependencies.some((dependency) => (
|
|
70
|
+
dependency.normalizedName === occurrence.normalizedName
|
|
71
|
+
&& dependency.type === occurrence.dependencyType
|
|
72
|
+
&& dependency.manifest === occurrence.manifest
|
|
73
|
+
));
|
|
74
|
+
if (!declared) {
|
|
75
|
+
errors.push(`${occurrence.packageId} does not match a declared dependency in ${occurrence.projectId}`);
|
|
76
|
+
}
|
|
77
|
+
if (project.ecosystem !== occurrence.ecosystem) {
|
|
78
|
+
errors.push(`${occurrence.packageId} ecosystem does not match project ${occurrence.projectId}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (errors.length > 0) {
|
|
82
|
+
throw new UsageDiscoveryInputError(`invalid dependency references: ${errors.sort().join('; ')}.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function loadUsageDiscoveryInputs(sources, options = {}) {
|
|
87
|
+
if (!sources?.projectManifest || !sources?.versionAnalysis) {
|
|
88
|
+
throw new UsageDiscoveryInputError('projectManifest and versionAnalysis sources are required.');
|
|
89
|
+
}
|
|
90
|
+
const projectArtifact = artifactFor(
|
|
91
|
+
sources.projectManifest,
|
|
92
|
+
options.projectManifestArtifact,
|
|
93
|
+
DEFAULT_MANIFEST_PATH,
|
|
94
|
+
'Project Manifest'
|
|
95
|
+
);
|
|
96
|
+
const versionArtifact = artifactFor(
|
|
97
|
+
sources.versionAnalysis,
|
|
98
|
+
options.versionAnalysisArtifact,
|
|
99
|
+
DEFAULT_VERSION_ANALYSIS_PATH,
|
|
100
|
+
'Version Analysis'
|
|
101
|
+
);
|
|
102
|
+
const [projectInput, versionBytes] = await Promise.all([
|
|
103
|
+
loadProjectManifestInput(sources.projectManifest, { artifact: projectArtifact }),
|
|
104
|
+
readBytes(sources.versionAnalysis, 'Version Analysis')
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
let versionAnalysis;
|
|
108
|
+
try {
|
|
109
|
+
versionAnalysis = JSON.parse(versionBytes.toString('utf8'));
|
|
110
|
+
} catch {
|
|
111
|
+
throw new UsageDiscoveryInputError('Version Analysis bytes are not valid JSON.');
|
|
112
|
+
}
|
|
113
|
+
if (versionAnalysis?.schemaVersion !== VERSION_ANALYSIS_SCHEMA_VERSION) {
|
|
114
|
+
throw new UsageDiscoveryInputError(
|
|
115
|
+
`unsupported Version Analysis schema version; expected ${VERSION_ANALYSIS_SCHEMA_VERSION}.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
validateVersionAnalysisManifest(versionAnalysis);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new UsageDiscoveryInputError(error.message);
|
|
122
|
+
}
|
|
123
|
+
validateLineage(projectInput, versionAnalysis);
|
|
124
|
+
validateDependencyReferences(projectInput.manifest, versionAnalysis);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
projectManifest: projectInput.manifest,
|
|
128
|
+
versionAnalysis,
|
|
129
|
+
input: {
|
|
130
|
+
projectManifest: projectInput.input.projectManifest,
|
|
131
|
+
versionAnalysis: {
|
|
132
|
+
schemaVersion: versionAnalysis.schemaVersion,
|
|
133
|
+
artifact: versionArtifact,
|
|
134
|
+
artifactDigest: digest(versionBytes)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { parseJavaScriptSource } from './parser.js';
|
|
2
|
+
|
|
3
|
+
export const JAVASCRIPT_USAGE_ANALYZER_ID = 'javascript-typescript';
|
|
4
|
+
export const JAVASCRIPT_USAGE_ANALYZER_VERSION = '1.0.0';
|
|
5
|
+
export const JAVASCRIPT_SOURCE_EXTENSIONS = Object.freeze([
|
|
6
|
+
'.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
const SKIPPED_KEYS = new Set(['loc', 'start', 'end', 'extra', 'errors', 'tokens', 'comments']);
|
|
10
|
+
|
|
11
|
+
function childEntries(node) {
|
|
12
|
+
const entries = [];
|
|
13
|
+
for (const [key, value] of Object.entries(node)) {
|
|
14
|
+
if (SKIPPED_KEYS.has(key)) continue;
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
for (const child of value) {
|
|
17
|
+
if (child && typeof child.type === 'string') entries.push([child, key]);
|
|
18
|
+
}
|
|
19
|
+
} else if (value && typeof value.type === 'string') entries.push([value, key]);
|
|
20
|
+
}
|
|
21
|
+
return entries;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function walk(node, visitor, parent = null, key = null, ancestors = []) {
|
|
25
|
+
visitor(node, parent, key, ancestors);
|
|
26
|
+
for (const [child, childKey] of childEntries(node)) {
|
|
27
|
+
walk(child, visitor, node, childKey, [...ancestors, node]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function importedName(specifier) {
|
|
32
|
+
if (specifier.type === 'ImportDefaultSpecifier') return 'default';
|
|
33
|
+
if (specifier.type === 'ImportNamespaceSpecifier') return '*';
|
|
34
|
+
return specifier.imported?.name ?? specifier.imported?.value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function exportedName(specifier) {
|
|
38
|
+
if (specifier.type === 'ExportNamespaceSpecifier') return '*';
|
|
39
|
+
return specifier.local?.name ?? specifier.local?.value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function moduleName(node) {
|
|
43
|
+
return typeof node?.value === 'string' ? node.value : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function npmPackageName(specifier) {
|
|
47
|
+
if (typeof specifier !== 'string' || specifier.length === 0) return null;
|
|
48
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')
|
|
49
|
+
|| specifier.startsWith('node:') || /^[a-z][a-z+.-]*:/i.test(specifier)) return null;
|
|
50
|
+
const parts = specifier.split('/');
|
|
51
|
+
return specifier.startsWith('@') && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createDependencyMatcher(dependencies) {
|
|
55
|
+
const candidates = [...dependencies]
|
|
56
|
+
.filter((item) => typeof item.name === 'string' && item.name.length > 0)
|
|
57
|
+
.sort((left, right) => right.name.length - left.name.length || left.name.localeCompare(right.name));
|
|
58
|
+
return (specifier) => {
|
|
59
|
+
const root = npmPackageName(specifier);
|
|
60
|
+
if (!root) return null;
|
|
61
|
+
return candidates.find((item) => root === item.name) ?? null;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function patternNames(pattern, names = []) {
|
|
66
|
+
if (!pattern) return names;
|
|
67
|
+
if (pattern.type === 'Identifier') names.push(pattern.name);
|
|
68
|
+
else if (pattern.type === 'RestElement') patternNames(pattern.argument, names);
|
|
69
|
+
else if (pattern.type === 'AssignmentPattern') patternNames(pattern.left, names);
|
|
70
|
+
else if (pattern.type === 'ObjectPattern') {
|
|
71
|
+
for (const property of pattern.properties) {
|
|
72
|
+
patternNames(property.type === 'RestElement' ? property.argument : property.value, names);
|
|
73
|
+
}
|
|
74
|
+
} else if (pattern.type === 'ArrayPattern') {
|
|
75
|
+
for (const element of pattern.elements) patternNames(element, names);
|
|
76
|
+
} else if (pattern.type === 'TSParameterProperty') patternNames(pattern.parameter, names);
|
|
77
|
+
return names;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function nearestScope(ancestors, kind = 'lexical') {
|
|
81
|
+
const candidates = [...ancestors].reverse();
|
|
82
|
+
if (kind === 'var') {
|
|
83
|
+
return candidates.find((node) => node.type === 'Program' || /^(Function)/.test(node.type));
|
|
84
|
+
}
|
|
85
|
+
return candidates.find((node) => (
|
|
86
|
+
node.type === 'Program' || node.type === 'BlockStatement' || node.type === 'CatchClause'
|
|
87
|
+
|| /^(Function)/.test(node.type)
|
|
88
|
+
));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function addShadow(shadows, names, scope) {
|
|
92
|
+
if (!scope || typeof scope.start !== 'number' || typeof scope.end !== 'number') return;
|
|
93
|
+
for (const name of names) {
|
|
94
|
+
if (!shadows.has(name)) shadows.set(name, []);
|
|
95
|
+
shadows.get(name).push([scope.start, scope.end]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectShadowRanges(program, importedBindings) {
|
|
100
|
+
const shadows = new Map();
|
|
101
|
+
walk(program, (node, parent, key, ancestors) => {
|
|
102
|
+
if (node.type === 'VariableDeclaration') {
|
|
103
|
+
const names = node.declarations.flatMap((declaration) => patternNames(declaration.id));
|
|
104
|
+
addShadow(shadows, names.filter((name) => importedBindings.has(name)), nearestScope(ancestors, node.kind));
|
|
105
|
+
} else if (/^Function/.test(node.type)) {
|
|
106
|
+
const names = node.params.flatMap((parameter) => patternNames(parameter));
|
|
107
|
+
addShadow(shadows, names.filter((name) => importedBindings.has(name)), node);
|
|
108
|
+
if (node.type === 'FunctionDeclaration' && node.id && importedBindings.has(node.id.name)) {
|
|
109
|
+
addShadow(shadows, [node.id.name], nearestScope(ancestors));
|
|
110
|
+
} else if (node.type === 'FunctionExpression' && node.id && importedBindings.has(node.id.name)) {
|
|
111
|
+
addShadow(shadows, [node.id.name], node.body);
|
|
112
|
+
}
|
|
113
|
+
} else if (node.type === 'ClassDeclaration' && node.id && importedBindings.has(node.id.name)) {
|
|
114
|
+
addShadow(shadows, [node.id.name], nearestScope(ancestors));
|
|
115
|
+
} else if (node.type === 'CatchClause') {
|
|
116
|
+
const names = patternNames(node.param).filter((name) => importedBindings.has(name));
|
|
117
|
+
addShadow(shadows, names, node.body);
|
|
118
|
+
} else if (/^TS(?:Interface|TypeAlias|Enum|Module)Declaration$/.test(node.type)
|
|
119
|
+
&& node.id?.name && importedBindings.has(node.id.name)) {
|
|
120
|
+
addShadow(shadows, [node.id.name], nearestScope(ancestors));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return shadows;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isShadowed(shadows, name, node) {
|
|
127
|
+
return (shadows.get(name) ?? []).some(([start, end]) => node.start >= start && node.end <= end);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isDeclarationIdentifier(node, parent, key) {
|
|
131
|
+
if (!parent) return false;
|
|
132
|
+
if (parent.type.startsWith('Import')) return true;
|
|
133
|
+
if ((parent.type === 'VariableDeclarator' || parent.type === 'AssignmentPattern') && key === 'id') return true;
|
|
134
|
+
if (/^(Function|Class)/.test(parent.type) && (key === 'id' || key === 'params')) return true;
|
|
135
|
+
if ((parent.type === 'RestElement' || parent.type === 'TSParameterProperty') && key === 'argument') return true;
|
|
136
|
+
if (parent.type === 'CatchClause' && key === 'param') return true;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isReferenceIdentifier(node, parent, key) {
|
|
141
|
+
if (!parent || isDeclarationIdentifier(node, parent, key)) return false;
|
|
142
|
+
if ((parent.type === 'MemberExpression' || parent.type === 'OptionalMemberExpression')
|
|
143
|
+
&& key === 'property' && !parent.computed) return false;
|
|
144
|
+
if (parent.type === 'JSXMemberExpression' && key === 'property') return false;
|
|
145
|
+
if (parent.type === 'JSXAttribute' && key === 'name') return false;
|
|
146
|
+
if ((parent.type === 'ObjectProperty' || parent.type === 'ObjectMethod' || parent.type === 'ClassMethod'
|
|
147
|
+
|| parent.type === 'ClassProperty' || parent.type === 'ClassAccessorProperty')
|
|
148
|
+
&& key === 'key' && !parent.computed) return false;
|
|
149
|
+
if (parent.type === 'ExportSpecifier' && key === 'exported') return false;
|
|
150
|
+
if (parent.type === 'LabeledStatement' || parent.type === 'BreakStatement' || parent.type === 'ContinueStatement') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (parent.type.startsWith('TS') && ['key', 'id'].includes(key)
|
|
154
|
+
&& !['TSTypeReference', 'TSExpressionWithTypeArguments', 'TSTypeQuery'].includes(parent.type)) return false;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function namespaceMember(node, parent, key) {
|
|
159
|
+
if ((parent?.type === 'MemberExpression' || parent?.type === 'OptionalMemberExpression')
|
|
160
|
+
&& key === 'object') {
|
|
161
|
+
if (!parent.computed && parent.property.type === 'Identifier') return parent.property.name;
|
|
162
|
+
if (parent.computed && parent.property.type === 'StringLiteral') return parent.property.value;
|
|
163
|
+
}
|
|
164
|
+
if (node.type === 'JSXIdentifier' && parent?.type === 'JSXMemberExpression' && key === 'object') {
|
|
165
|
+
return parent.property.name;
|
|
166
|
+
}
|
|
167
|
+
return '*';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function addUsage(usages, dependency, symbol, file) {
|
|
171
|
+
if (!dependency || (symbol !== null && (typeof symbol !== 'string' || symbol.length === 0))) return;
|
|
172
|
+
usages.set(`${dependency.packageId}\0${symbol ?? '<dependency>'}`, {
|
|
173
|
+
packageId: dependency.packageId,
|
|
174
|
+
dependency: dependency.name,
|
|
175
|
+
symbol,
|
|
176
|
+
file
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function analyzeJavaScriptUsage({ source, file, dependencies }) {
|
|
181
|
+
const ast = parseJavaScriptSource(source, file);
|
|
182
|
+
const matchDependency = createDependencyMatcher(dependencies);
|
|
183
|
+
const bindings = new Map();
|
|
184
|
+
const usages = new Map();
|
|
185
|
+
|
|
186
|
+
for (const statement of ast.program.body) {
|
|
187
|
+
if (statement.type === 'ImportDeclaration') {
|
|
188
|
+
const dependency = matchDependency(moduleName(statement.source));
|
|
189
|
+
if (!dependency) continue;
|
|
190
|
+
if (statement.specifiers.length === 0) addUsage(usages, dependency, null, file);
|
|
191
|
+
for (const specifier of statement.specifiers) {
|
|
192
|
+
bindings.set(specifier.local.name, {
|
|
193
|
+
dependency,
|
|
194
|
+
symbol: importedName(specifier),
|
|
195
|
+
namespace: specifier.type === 'ImportNamespaceSpecifier',
|
|
196
|
+
declaration: specifier.local
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
} else if (statement.type === 'ExportNamedDeclaration' && statement.source) {
|
|
200
|
+
const dependency = matchDependency(moduleName(statement.source));
|
|
201
|
+
for (const specifier of statement.specifiers) {
|
|
202
|
+
addUsage(usages, dependency, exportedName(specifier), file);
|
|
203
|
+
}
|
|
204
|
+
} else if (statement.type === 'ExportAllDeclaration') {
|
|
205
|
+
addUsage(usages, matchDependency(moduleName(statement.source)), '*', file);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const shadows = collectShadowRanges(ast.program, bindings);
|
|
210
|
+
walk(ast.program, (node, parent, key) => {
|
|
211
|
+
if (node.type === 'ImportExpression') {
|
|
212
|
+
addUsage(usages, matchDependency(moduleName(node.source)), '*', file);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (node.type !== 'Identifier' && node.type !== 'JSXIdentifier') return;
|
|
216
|
+
const binding = bindings.get(node.name);
|
|
217
|
+
if (!binding || node === binding.declaration || isShadowed(shadows, node.name, node)
|
|
218
|
+
|| !isReferenceIdentifier(node, parent, key)) return;
|
|
219
|
+
addUsage(
|
|
220
|
+
usages,
|
|
221
|
+
binding.dependency,
|
|
222
|
+
binding.namespace ? namespaceMember(node, parent, key) : binding.symbol,
|
|
223
|
+
file
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return [...usages.values()].sort((left, right) => (
|
|
228
|
+
left.packageId.localeCompare(right.packageId) || (left.symbol ?? '').localeCompare(right.symbol ?? '')
|
|
229
|
+
));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function createJavaScriptUsageAnalyzer() {
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
id: JAVASCRIPT_USAGE_ANALYZER_ID,
|
|
235
|
+
version: JAVASCRIPT_USAGE_ANALYZER_VERSION,
|
|
236
|
+
ecosystems: Object.freeze(['node']),
|
|
237
|
+
extensions: JAVASCRIPT_SOURCE_EXTENSIONS,
|
|
238
|
+
analyze: analyzeJavaScriptUsage
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
|
|
3
|
+
export function parseJavaScriptSource(source, filePath = '<source>') {
|
|
4
|
+
return parse(source, {
|
|
5
|
+
sourceFilename: filePath,
|
|
6
|
+
sourceType: 'unambiguous',
|
|
7
|
+
allowAwaitOutsideFunction: true,
|
|
8
|
+
allowReturnOutsideFunction: true,
|
|
9
|
+
createImportExpressions: true,
|
|
10
|
+
plugins: [
|
|
11
|
+
'jsx',
|
|
12
|
+
'typescript',
|
|
13
|
+
'decoratorAutoAccessors',
|
|
14
|
+
['decorators', { decoratorsBeforeExport: true }],
|
|
15
|
+
'explicitResourceManagement',
|
|
16
|
+
'importAttributes'
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const parseJavaScriptModule = parseJavaScriptSource;
|