@salesforce/uibundle-sourcemap-validator-engine 11.35.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.
Files changed (63) hide show
  1. package/LICENSE.txt +82 -0
  2. package/README.md +243 -0
  3. package/dist/engine.d.ts +17 -0
  4. package/dist/engine.d.ts.map +1 -0
  5. package/dist/engine.js +142 -0
  6. package/dist/engine.js.map +1 -0
  7. package/dist/index.d.ts +19 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +18 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/plugin.d.ts +12 -0
  12. package/dist/plugin.d.ts.map +1 -0
  13. package/dist/plugin.js +22 -0
  14. package/dist/plugin.js.map +1 -0
  15. package/dist/rules.d.ts +9 -0
  16. package/dist/rules.d.ts.map +1 -0
  17. package/dist/rules.js +108 -0
  18. package/dist/rules.js.map +1 -0
  19. package/dist/validators/classification.d.ts +51 -0
  20. package/dist/validators/classification.d.ts.map +1 -0
  21. package/dist/validators/classification.js +120 -0
  22. package/dist/validators/classification.js.map +1 -0
  23. package/dist/validators/coverage-analysis.d.ts +32 -0
  24. package/dist/validators/coverage-analysis.d.ts.map +1 -0
  25. package/dist/validators/coverage-analysis.js +147 -0
  26. package/dist/validators/coverage-analysis.js.map +1 -0
  27. package/dist/validators/invalid-source-references.d.ts +9 -0
  28. package/dist/validators/invalid-source-references.d.ts.map +1 -0
  29. package/dist/validators/invalid-source-references.js +56 -0
  30. package/dist/validators/invalid-source-references.js.map +1 -0
  31. package/dist/validators/missing-sourcemap.d.ts +9 -0
  32. package/dist/validators/missing-sourcemap.d.ts.map +1 -0
  33. package/dist/validators/missing-sourcemap.js +92 -0
  34. package/dist/validators/missing-sourcemap.js.map +1 -0
  35. package/dist/validators/path-leakage.d.ts +9 -0
  36. package/dist/validators/path-leakage.d.ts.map +1 -0
  37. package/dist/validators/path-leakage.js +38 -0
  38. package/dist/validators/path-leakage.js.map +1 -0
  39. package/dist/validators/source-content-verification.d.ts +13 -0
  40. package/dist/validators/source-content-verification.d.ts.map +1 -0
  41. package/dist/validators/source-content-verification.js +465 -0
  42. package/dist/validators/source-content-verification.js.map +1 -0
  43. package/dist/validators/sourcemap-io.d.ts +21 -0
  44. package/dist/validators/sourcemap-io.d.ts.map +1 -0
  45. package/dist/validators/sourcemap-io.js +44 -0
  46. package/dist/validators/sourcemap-io.js.map +1 -0
  47. package/dist/validators/structural-coherence.d.ts +29 -0
  48. package/dist/validators/structural-coherence.d.ts.map +1 -0
  49. package/dist/validators/structural-coherence.js +247 -0
  50. package/dist/validators/structural-coherence.js.map +1 -0
  51. package/dist/validators/token-consistency.d.ts +32 -0
  52. package/dist/validators/token-consistency.d.ts.map +1 -0
  53. package/dist/validators/token-consistency.js +260 -0
  54. package/dist/validators/token-consistency.js.map +1 -0
  55. package/dist/validators/types.d.ts +19 -0
  56. package/dist/validators/types.d.ts.map +1 -0
  57. package/dist/validators/types.js +7 -0
  58. package/dist/validators/types.js.map +1 -0
  59. package/dist/validators/vlq-integrity.d.ts +9 -0
  60. package/dist/validators/vlq-integrity.d.ts.map +1 -0
  61. package/dist/validators/vlq-integrity.js +93 -0
  62. package/dist/validators/vlq-integrity.js.map +1 -0
  63. package/package.json +63 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ export interface RawSourceMap {
7
+ version: number;
8
+ file?: string;
9
+ sourceRoot?: string;
10
+ sources: (string | null)[];
11
+ sourcesContent?: (string | null)[];
12
+ names?: string[];
13
+ mappings: string;
14
+ }
15
+ export interface LoadedSourceMap {
16
+ path: string;
17
+ map: RawSourceMap;
18
+ }
19
+ export declare function collectSourceMaps(root: string): Promise<LoadedSourceMap[]>;
20
+ export declare function walk(root: string, visit: (file: string) => Promise<void>): Promise<void>;
21
+ //# sourceMappingURL=sourcemap-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourcemap-io.d.ts","sourceRoot":"","sources":["../../src/validators/sourcemap-io.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,YAAY,CAAC;CAClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAehF;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9F"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import * as path from "node:path";
8
+ export async function collectSourceMaps(root) {
9
+ const out = [];
10
+ await walk(root, async (file) => {
11
+ if (!file.endsWith(".js.map"))
12
+ return;
13
+ try {
14
+ const raw = await fs.readFile(file, "utf8");
15
+ const map = JSON.parse(raw);
16
+ if (typeof map?.mappings === "string" && Array.isArray(map?.sources)) {
17
+ out.push({ path: file, map });
18
+ }
19
+ }
20
+ catch {
21
+ // Malformed JSON is surfaced by the vlq-integrity validator instead.
22
+ }
23
+ });
24
+ return out;
25
+ }
26
+ export async function walk(root, visit) {
27
+ let entries;
28
+ try {
29
+ entries = await fs.readdir(root, { withFileTypes: true });
30
+ }
31
+ catch {
32
+ return;
33
+ }
34
+ for (const entry of entries) {
35
+ const full = path.join(root, entry.name);
36
+ if (entry.isDirectory()) {
37
+ await walk(full, visit);
38
+ }
39
+ else if (entry.isFile()) {
40
+ await visit(full);
41
+ }
42
+ }
43
+ }
44
+ //# sourceMappingURL=sourcemap-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourcemap-io.js","sourceRoot":"","sources":["../../src/validators/sourcemap-io.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAiBlC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IACnD,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC/B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO;QACtC,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;YAC5C,IAAI,OAAO,GAAG,EAAE,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC;gBACtE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YAC/B,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,qEAAqE;QACtE,CAAC;IACF,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAY,EAAE,KAAsC;IAC9E,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;IACR,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3B,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;AACF,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { TraceMap } from "@jridgewell/trace-mapping";
7
+ import type { ValidatorResult } from "./types.js";
8
+ export declare const STRUCTURAL_COHERENCE_RULE = "structural-coherence";
9
+ interface BoundsViolation {
10
+ sourceFile: string;
11
+ claimedLine: number;
12
+ claimedCol: number;
13
+ actualLines: number;
14
+ }
15
+ export interface StructuralCoherenceOptions {
16
+ sourcePath: string;
17
+ distPath: string;
18
+ }
19
+ export declare function validateStructuralCoherence(options: StructuralCoherenceOptions): Promise<ValidatorResult>;
20
+ export declare function analyzeCoherence(tracer: TraceMap, sourceContents: Map<string, string>): {
21
+ totalMappingsChecked: number;
22
+ boundsViolations: BoundsViolation[];
23
+ whitespaceOnlyMappings: number;
24
+ suspiciousJumpRatio: number;
25
+ };
26
+ /** Ported from coherence.rs::points_to_whitespace_or_comment. */
27
+ export declare function pointsToWhitespaceOrComment(source: string, line0: number, col0: number): boolean;
28
+ export {};
29
+ //# sourceMappingURL=structural-coherence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-coherence.d.ts","sourceRoot":"","sources":["../../src/validators/structural-coherence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,QAAQ,EAAiC,MAAM,2BAA2B,CAAC;AAGpF,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpE,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAQhE,UAAU,eAAe;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,2BAA2B,CAChD,OAAO,EAAE,0BAA0B,GACjC,OAAO,CAAC,eAAe,CAAC,CAgF1B;AAED,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,QAAQ,EAChB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IACF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;CAC5B,CA6GA;AAED,iEAAiE;AACjE,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAqBhG"}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import * as path from "node:path";
8
+ import { TraceMap, eachMapping, sourceContentFor } from "@jridgewell/trace-mapping";
9
+ import { isAsset, isDependency, normalizeSourcePath } from "./classification.js";
10
+ import { walk } from "./sourcemap-io.js";
11
+ export const STRUCTURAL_COHERENCE_RULE = "structural-coherence";
12
+ // Ported from uibundle-sourcemap-validator/src/coherence.rs
13
+ const WHITESPACE_SAMPLE_INTERVAL = 10;
14
+ const WHITESPACE_SUSPICION_THRESHOLD = 0.8;
15
+ const JUMP_RATIO_WARN = 0.5;
16
+ const MAX_LISTED_BOUNDS = 10;
17
+ export async function validateStructuralCoherence(options) {
18
+ let sourceStat, distStat;
19
+ try {
20
+ sourceStat = await fs.stat(options.sourcePath);
21
+ }
22
+ catch {
23
+ return { findings: [], skipped: { reason: `source path not found: ${options.sourcePath}` } };
24
+ }
25
+ try {
26
+ distStat = await fs.stat(options.distPath);
27
+ }
28
+ catch {
29
+ return { findings: [], skipped: { reason: `dist path not found: ${options.distPath}` } };
30
+ }
31
+ if (!sourceStat.isDirectory() || !distStat.isDirectory()) {
32
+ return { findings: [], skipped: { reason: "source and dist must both be directories" } };
33
+ }
34
+ const rawIndex = await indexSourceFiles(options.sourcePath);
35
+ const sourceIndex = expandIndexWithBase(rawIndex, path.basename(options.sourcePath));
36
+ const findings = [];
37
+ await walk(options.distPath, async (jsPath) => {
38
+ if (!jsPath.endsWith(".js"))
39
+ return;
40
+ const mapPath = `${jsPath}.map`;
41
+ let mapRaw;
42
+ try {
43
+ mapRaw = await fs.readFile(mapPath, "utf8");
44
+ }
45
+ catch {
46
+ return;
47
+ }
48
+ let tracer;
49
+ try {
50
+ tracer = new TraceMap(mapRaw);
51
+ }
52
+ catch {
53
+ return;
54
+ }
55
+ const report = analyzeCoherence(tracer, sourceIndex);
56
+ if (report.totalMappingsChecked === 0)
57
+ return;
58
+ // Bounds violations
59
+ if (report.boundsViolations.length > 0) {
60
+ findings.push({
61
+ ruleName: STRUCTURAL_COHERENCE_RULE,
62
+ message: `${report.boundsViolations.length} sourcemap mapping(s) point out of bounds of the referenced source file.`,
63
+ file: mapPath,
64
+ });
65
+ for (const v of report.boundsViolations.slice(0, MAX_LISTED_BOUNDS)) {
66
+ findings.push({
67
+ ruleName: STRUCTURAL_COHERENCE_RULE,
68
+ message: `Out-of-bounds: "${v.sourceFile}" claimed line ${v.claimedLine + 1}:${v.claimedCol + 1} but file has ${v.actualLines} line(s).`,
69
+ file: mapPath,
70
+ });
71
+ }
72
+ }
73
+ // Whitespace/comment sampling
74
+ const sampled = Math.floor(report.totalMappingsChecked / WHITESPACE_SAMPLE_INTERVAL);
75
+ if (sampled > 0) {
76
+ const wsRatio = report.whitespaceOnlyMappings / sampled;
77
+ if (wsRatio > WHITESPACE_SUSPICION_THRESHOLD) {
78
+ findings.push({
79
+ ruleName: STRUCTURAL_COHERENCE_RULE,
80
+ message: `${(wsRatio * 100).toFixed(1)}% of sampled mappings point to whitespace or comments (${report.whitespaceOnlyMappings}/${sampled}). Threshold is ${WHITESPACE_SUSPICION_THRESHOLD * 100}%; sourcemap tokens may be fabricated to satisfy coverage without pointing at real code.`,
81
+ file: mapPath,
82
+ });
83
+ }
84
+ }
85
+ // Cross-file jump ratio
86
+ if (report.suspiciousJumpRatio > JUMP_RATIO_WARN) {
87
+ findings.push({
88
+ ruleName: STRUCTURAL_COHERENCE_RULE,
89
+ message: `Cross-file jump ratio ${report.suspiciousJumpRatio.toFixed(2)} exceeds ${JUMP_RATIO_WARN}: majority of consecutive tokens on the same generated line jump between source files, which is unusual for real bundler output.`,
90
+ file: mapPath,
91
+ });
92
+ }
93
+ });
94
+ return { findings };
95
+ }
96
+ export function analyzeCoherence(tracer, sourceContents) {
97
+ // Cache line lengths per submitted normalized source path
98
+ const submittedLineLens = new Map();
99
+ for (const [key, content] of sourceContents) {
100
+ submittedLineLens.set(key, content.split("\n").map((l) => l.length));
101
+ }
102
+ // Cache line lengths per embedded source name (as it appears in sources[])
103
+ const embeddedLineLens = new Map();
104
+ const embeddedText = new Map();
105
+ let totalMappingsChecked = 0;
106
+ const boundsViolations = [];
107
+ let whitespaceOnlyMappings = 0;
108
+ let prevSource = null;
109
+ let prevDstLine = null;
110
+ let consecutivePairs = 0;
111
+ let crossFileJumps = 0;
112
+ let sampleIndex = 0;
113
+ eachMapping(tracer, (m) => {
114
+ if (m.source == null || m.originalLine == null || m.originalColumn == null)
115
+ return;
116
+ const srcRaw = m.source;
117
+ const normalized = normalizeSourcePath(srcRaw);
118
+ if (isAsset(normalized) || isDependency(normalized))
119
+ return;
120
+ // 0-based line/col in the Rust world (sourcemap crate returns 0-based),
121
+ // 1-based in trace-mapping's EachMapping. We normalize to 0-based here
122
+ // for direct parity with the ported algorithm.
123
+ const srcLine = m.originalLine - 1;
124
+ const srcCol = m.originalColumn;
125
+ const dstLine = m.generatedLine - 1;
126
+ let lineLens = submittedLineLens.get(normalized);
127
+ if (!lineLens) {
128
+ // Try embedded — cache computed line lengths per raw source name.
129
+ let cached = embeddedLineLens.get(srcRaw);
130
+ if (!cached) {
131
+ try {
132
+ const contents = sourceContentFor(tracer, srcRaw);
133
+ if (contents != null) {
134
+ cached = contents.split("\n").map((l) => l.length);
135
+ embeddedLineLens.set(srcRaw, cached);
136
+ embeddedText.set(srcRaw, contents);
137
+ }
138
+ }
139
+ catch {
140
+ // ignore
141
+ }
142
+ }
143
+ if (!cached)
144
+ return;
145
+ lineLens = cached;
146
+ }
147
+ totalMappingsChecked++;
148
+ // --- Check 1: bounds ---
149
+ const actualLines = lineLens.length;
150
+ if (srcLine >= actualLines) {
151
+ boundsViolations.push({
152
+ sourceFile: normalized,
153
+ claimedLine: srcLine,
154
+ claimedCol: srcCol,
155
+ actualLines,
156
+ });
157
+ }
158
+ else {
159
+ const lineLen = lineLens[srcLine];
160
+ if (srcCol > lineLen) {
161
+ boundsViolations.push({
162
+ sourceFile: normalized,
163
+ claimedLine: srcLine,
164
+ claimedCol: srcCol,
165
+ actualLines,
166
+ });
167
+ }
168
+ }
169
+ // --- Check 2: whitespace/comment sampling (every 10th mapping) ---
170
+ if (sampleIndex % WHITESPACE_SAMPLE_INTERVAL === 0 && srcLine < actualLines) {
171
+ const sourceText = sourceContents.get(normalized) ?? embeddedText.get(srcRaw) ?? null;
172
+ if (sourceText != null && pointsToWhitespaceOrComment(sourceText, srcLine, srcCol)) {
173
+ whitespaceOnlyMappings++;
174
+ }
175
+ }
176
+ sampleIndex++;
177
+ // --- Check 3: cross-file jump ratio (consecutive tokens on same dst line) ---
178
+ if (prevDstLine !== null && dstLine === prevDstLine) {
179
+ if (prevSource !== null) {
180
+ consecutivePairs++;
181
+ if (prevSource !== srcRaw)
182
+ crossFileJumps++;
183
+ }
184
+ }
185
+ prevSource = srcRaw;
186
+ prevDstLine = dstLine;
187
+ });
188
+ const suspiciousJumpRatio = consecutivePairs > 0 ? crossFileJumps / consecutivePairs : 0;
189
+ return {
190
+ totalMappingsChecked,
191
+ boundsViolations,
192
+ whitespaceOnlyMappings,
193
+ suspiciousJumpRatio,
194
+ };
195
+ }
196
+ /** Ported from coherence.rs::points_to_whitespace_or_comment. */
197
+ export function pointsToWhitespaceOrComment(source, line0, col0) {
198
+ const lines = source.split("\n");
199
+ if (line0 >= lines.length)
200
+ return false;
201
+ const lineText = lines[line0];
202
+ if (col0 >= lineText.length)
203
+ return true;
204
+ const fromCol = lineText.slice(col0);
205
+ const trimmed = fromCol.replace(/^[\s]+/, "");
206
+ if (trimmed.length === 0)
207
+ return true;
208
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
209
+ return true;
210
+ const lineTrimmed = lineText.trim();
211
+ if (lineTrimmed.startsWith("//") ||
212
+ lineTrimmed.startsWith("/*") ||
213
+ lineTrimmed.startsWith("*") ||
214
+ lineTrimmed.endsWith("*/"))
215
+ return true;
216
+ return false;
217
+ }
218
+ const INDEX_IGNORE_PREFIXES = ["node_modules", ".git", "dist"];
219
+ /**
220
+ * Add `<sourcePathBase>/<rel>` aliases so `sources[]` entries like
221
+ * `../src/foo.ts` (which normalize to `src/foo.ts`) find the file even
222
+ * though the on-disk index is keyed by the path relative to `src/`.
223
+ */
224
+ function expandIndexWithBase(index, base) {
225
+ const out = new Map(index);
226
+ for (const [rel, content] of index) {
227
+ out.set(`${base}/${rel}`, content);
228
+ }
229
+ return out;
230
+ }
231
+ async function indexSourceFiles(sourcePath) {
232
+ const index = new Map();
233
+ await walk(sourcePath, async (abs) => {
234
+ const rel = path.relative(sourcePath, abs);
235
+ if (INDEX_IGNORE_PREFIXES.some((prefix) => rel.startsWith(prefix)))
236
+ return;
237
+ try {
238
+ const content = await fs.readFile(abs, "utf8");
239
+ index.set(rel, content);
240
+ }
241
+ catch {
242
+ // binary or unreadable — skip
243
+ }
244
+ });
245
+ return index;
246
+ }
247
+ //# sourceMappingURL=structural-coherence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-coherence.js","sourceRoot":"","sources":["../../src/validators/structural-coherence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { TraceMap } from "@jridgewell/trace-mapping";
7
+ import type { ValidatorResult } from "./types.js";
8
+ export declare const TOKEN_CONSISTENCY_RULE = "token-consistency";
9
+ export type TokenCategory = "StringLiteral" | "NumericLiteral" | "Identifier" | "Punctuation" | "Other";
10
+ interface NameMismatch {
11
+ expectedName: string;
12
+ sourceFile: string;
13
+ line: number;
14
+ col: number;
15
+ foundText: string;
16
+ }
17
+ export interface TokenConsistencyOptions {
18
+ sourcePath: string;
19
+ distPath: string;
20
+ }
21
+ export declare function validateTokenConsistency(options: TokenConsistencyOptions): Promise<ValidatorResult>;
22
+ export declare function analyzeTokenConsistency(tracer: TraceMap, compiledJs: string, sourceContents: Map<string, string>): {
23
+ totalSampled: number;
24
+ consistent: number;
25
+ inconsistent: number;
26
+ nameMismatches: NameMismatch[];
27
+ consistencyScore: number;
28
+ };
29
+ /** Ported from token_check.rs::classify_token_at. */
30
+ export declare function classifyTokenAt(text: string, line0: number, col0: number): TokenCategory;
31
+ export {};
32
+ //# sourceMappingURL=token-consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-consistency.d.ts","sourceRoot":"","sources":["../../src/validators/token-consistency.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,QAAQ,EAAiC,MAAM,2BAA2B,CAAC;AAGpF,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpE,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAS1D,MAAM,MAAM,aAAa,GACtB,eAAe,GACf,gBAAgB,GAChB,YAAY,GACZ,aAAa,GACb,OAAO,CAAC;AAEX,UAAU,YAAY;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,uBAAuB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA4E1B;AAED,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,QAAQ,EAChB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;CACzB,CAwEA;AAED,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAWxF"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import * as path from "node:path";
8
+ import { TraceMap, eachMapping, sourceContentFor } from "@jridgewell/trace-mapping";
9
+ import { isAsset, isDependency, isVirtualSource, normalizeSourcePath } from "./classification.js";
10
+ import { walk } from "./sourcemap-io.js";
11
+ export const TOKEN_CONSISTENCY_RULE = "token-consistency";
12
+ // Ported from uibundle-sourcemap-validator/src/token_check.rs
13
+ const SAMPLE_INTERVAL = 20;
14
+ const NAME_WINDOW_TOLERANCE = 3;
15
+ const VERDICT_SUSPICIOUS = 0.7;
16
+ const VERDICT_WARNING = 0.85;
17
+ const MAX_LISTED_MISMATCHES = 10;
18
+ export async function validateTokenConsistency(options) {
19
+ let sourceStat, distStat;
20
+ try {
21
+ sourceStat = await fs.stat(options.sourcePath);
22
+ }
23
+ catch {
24
+ return { findings: [], skipped: { reason: `source path not found: ${options.sourcePath}` } };
25
+ }
26
+ try {
27
+ distStat = await fs.stat(options.distPath);
28
+ }
29
+ catch {
30
+ return { findings: [], skipped: { reason: `dist path not found: ${options.distPath}` } };
31
+ }
32
+ if (!sourceStat.isDirectory() || !distStat.isDirectory()) {
33
+ return { findings: [], skipped: { reason: "source and dist must both be directories" } };
34
+ }
35
+ const rawIndex = await indexSourceFiles(options.sourcePath);
36
+ const sourceIndex = expandIndexWithBase(rawIndex, path.basename(options.sourcePath));
37
+ const findings = [];
38
+ await walk(options.distPath, async (jsPath) => {
39
+ if (!jsPath.endsWith(".js"))
40
+ return;
41
+ const mapPath = `${jsPath}.map`;
42
+ let mapRaw;
43
+ try {
44
+ mapRaw = await fs.readFile(mapPath, "utf8");
45
+ }
46
+ catch {
47
+ return;
48
+ }
49
+ let tracer;
50
+ try {
51
+ tracer = new TraceMap(mapRaw);
52
+ }
53
+ catch {
54
+ return;
55
+ }
56
+ let compiledJs;
57
+ try {
58
+ compiledJs = await fs.readFile(jsPath, "utf8");
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ const report = analyzeTokenConsistency(tracer, compiledJs, sourceIndex);
64
+ if (report.totalSampled === 0)
65
+ return;
66
+ if (report.consistencyScore < VERDICT_SUSPICIOUS) {
67
+ findings.push({
68
+ ruleName: TOKEN_CONSISTENCY_RULE,
69
+ message: `Token type consistency ${(report.consistencyScore * 100).toFixed(1)}% is below suspicious threshold ${VERDICT_SUSPICIOUS * 100}% (${report.consistent}/${report.totalSampled} sampled tokens agree between compiled and source positions). Sourcemap tokens appear fabricated.`,
70
+ file: mapPath,
71
+ });
72
+ }
73
+ else if (report.consistencyScore < VERDICT_WARNING) {
74
+ findings.push({
75
+ ruleName: TOKEN_CONSISTENCY_RULE,
76
+ message: `Token type consistency ${(report.consistencyScore * 100).toFixed(1)}% is below expected threshold ${VERDICT_WARNING * 100}% (${report.consistent}/${report.totalSampled} sampled tokens agree between compiled and source positions).`,
77
+ file: mapPath,
78
+ });
79
+ }
80
+ if (report.nameMismatches.length > 0) {
81
+ findings.push({
82
+ ruleName: TOKEN_CONSISTENCY_RULE,
83
+ message: `${report.nameMismatches.length} sourcemap name(s) do not exist at the claimed source position (±${NAME_WINDOW_TOLERANCE} col tolerance).`,
84
+ file: mapPath,
85
+ });
86
+ for (const m of report.nameMismatches.slice(0, MAX_LISTED_MISMATCHES)) {
87
+ findings.push({
88
+ ruleName: TOKEN_CONSISTENCY_RULE,
89
+ message: `Name mismatch: expected "${m.expectedName}" at ${m.sourceFile}:${m.line + 1}:${m.col + 1}, found "${m.foundText}".`,
90
+ file: mapPath,
91
+ });
92
+ }
93
+ }
94
+ });
95
+ return { findings };
96
+ }
97
+ export function analyzeTokenConsistency(tracer, compiledJs, sourceContents) {
98
+ const embeddedCache = new Map();
99
+ let totalSampled = 0;
100
+ let consistent = 0;
101
+ let inconsistent = 0;
102
+ const nameMismatches = [];
103
+ let idx = -1;
104
+ eachMapping(tracer, (m) => {
105
+ idx++;
106
+ if (idx % SAMPLE_INTERVAL !== 0)
107
+ return;
108
+ if (m.source == null || m.originalLine == null || m.originalColumn == null)
109
+ return;
110
+ const srcRaw = m.source;
111
+ const normalized = normalizeSourcePath(srcRaw);
112
+ // Skip third-party deps, static assets, and virtual bundler pseudo-sources.
113
+ // These aren't part of the submitted source tree — the corresponding rules
114
+ // in the Rust tool (coherence.rs) apply the same filter.
115
+ if (isDependency(normalized) || isAsset(normalized) || isVirtualSource(normalized))
116
+ return;
117
+ // Resolve source text: submitted first, then embedded
118
+ let sourceText = sourceContents.get(normalized) ?? null;
119
+ if (sourceText == null) {
120
+ if (embeddedCache.has(srcRaw)) {
121
+ sourceText = embeddedCache.get(srcRaw) ?? null;
122
+ }
123
+ else {
124
+ try {
125
+ const c = sourceContentFor(tracer, srcRaw);
126
+ embeddedCache.set(srcRaw, c);
127
+ sourceText = c;
128
+ }
129
+ catch {
130
+ embeddedCache.set(srcRaw, null);
131
+ }
132
+ }
133
+ }
134
+ if (sourceText == null)
135
+ return;
136
+ totalSampled++;
137
+ const srcLine = m.originalLine - 1;
138
+ const srcCol = m.originalColumn;
139
+ const dstLine = m.generatedLine - 1;
140
+ const dstCol = m.generatedColumn;
141
+ // --- Check 1: Name verification ---
142
+ if (m.name != null) {
143
+ const [found, foundText] = nameExistsNear(sourceText, srcLine, srcCol, m.name);
144
+ if (!found) {
145
+ nameMismatches.push({
146
+ expectedName: m.name,
147
+ sourceFile: normalized,
148
+ line: srcLine,
149
+ col: srcCol,
150
+ foundText,
151
+ });
152
+ }
153
+ }
154
+ // --- Check 2: Token category matching ---
155
+ const genCat = classifyTokenAt(compiledJs, dstLine, dstCol);
156
+ const srcCat = classifyTokenAt(sourceText, srcLine, srcCol);
157
+ if (categoriesAreConsistent(genCat, srcCat)) {
158
+ consistent++;
159
+ }
160
+ else {
161
+ inconsistent++;
162
+ }
163
+ });
164
+ const consistencyScore = totalSampled > 0 ? consistent / totalSampled : 1.0;
165
+ return { totalSampled, consistent, inconsistent, nameMismatches, consistencyScore };
166
+ }
167
+ /** Ported from token_check.rs::classify_token_at. */
168
+ export function classifyTokenAt(text, line0, col0) {
169
+ const lines = text.split("\n");
170
+ if (line0 >= lines.length)
171
+ return "Other";
172
+ const lineText = lines[line0];
173
+ if (col0 >= lineText.length)
174
+ return "Other";
175
+ const ch = lineText.charAt(col0);
176
+ if (ch === '"' || ch === "'" || ch === "`")
177
+ return "StringLiteral";
178
+ if (ch >= "0" && ch <= "9")
179
+ return "NumericLiteral";
180
+ if (/[A-Za-z_$]/.test(ch))
181
+ return "Identifier";
182
+ if (/\s/.test(ch))
183
+ return "Other";
184
+ return "Punctuation";
185
+ }
186
+ function categoriesAreConsistent(gen, src) {
187
+ if (gen === src)
188
+ return true;
189
+ if (gen === "Other" || src === "Other")
190
+ return true;
191
+ if ((gen === "NumericLiteral" && src === "Identifier") ||
192
+ (gen === "Identifier" && src === "NumericLiteral"))
193
+ return true;
194
+ return false;
195
+ }
196
+ /** Ported from token_check.rs::name_exists_near — ±3 col window. */
197
+ function nameExistsNear(source, line0, col0, expected) {
198
+ const lines = source.split("\n");
199
+ if (line0 >= lines.length)
200
+ return [false, ""];
201
+ const lineText = lines[line0];
202
+ for (let offset = 0; offset <= NAME_WINDOW_TOLERANCE; offset++) {
203
+ for (const dir of [1, -1]) {
204
+ const checkCol = offset === 0 ? col0 : col0 + offset * dir;
205
+ if (checkCol < 0)
206
+ continue;
207
+ const word = extractWordAt(lineText, checkCol, expected.length + 5);
208
+ if (word === expected)
209
+ return [true, word];
210
+ if (offset === 0)
211
+ break; // Only check offset=0 once
212
+ }
213
+ }
214
+ const found = extractWordAt(lineText, col0, expected.length + 10);
215
+ return [false, found];
216
+ }
217
+ function extractWordAt(line, col, maxLen) {
218
+ if (col >= line.length)
219
+ return "";
220
+ let out = "";
221
+ const end = Math.min(line.length, col + maxLen);
222
+ for (let i = col; i < end; i++) {
223
+ const c = line.charAt(i);
224
+ if (/[A-Za-z0-9_$]/.test(c))
225
+ out += c;
226
+ else
227
+ break;
228
+ }
229
+ return out;
230
+ }
231
+ const INDEX_IGNORE_PREFIXES = ["node_modules", ".git", "dist"];
232
+ /**
233
+ * Add `<sourcePathBase>/<rel>` aliases so `sources[]` entries like
234
+ * `../src/foo.ts` (which normalize to `src/foo.ts`) find the file even
235
+ * though the on-disk index is keyed by the path relative to `src/`.
236
+ */
237
+ function expandIndexWithBase(index, base) {
238
+ const out = new Map(index);
239
+ for (const [rel, content] of index) {
240
+ out.set(`${base}/${rel}`, content);
241
+ }
242
+ return out;
243
+ }
244
+ async function indexSourceFiles(sourcePath) {
245
+ const index = new Map();
246
+ await walk(sourcePath, async (abs) => {
247
+ const rel = path.relative(sourcePath, abs);
248
+ if (INDEX_IGNORE_PREFIXES.some((prefix) => rel.startsWith(prefix)))
249
+ return;
250
+ try {
251
+ const content = await fs.readFile(abs, "utf8");
252
+ index.set(rel, content);
253
+ }
254
+ catch {
255
+ // binary or unreadable — skip
256
+ }
257
+ });
258
+ return index;
259
+ }
260
+ //# sourceMappingURL=token-consistency.js.map