Skip to content

Commit 296d9ae

Browse files
authored
Merge pull request #14 from hbenl/more-scope-validations
Add more validations for scopes
2 parents e0e1d66 + 374c252 commit 296d9ae

File tree

1 file changed

+89
-3
lines changed

1 file changed

+89
-3
lines changed

src/validators/SourceMapScopesValidator.ts

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import { Validator } from "../util/Validator.js";
22
import { TestingFile } from "../util/TestingFile.js";
33
import { collectSourceFiles } from "../util/collectSourceFiles.js";
44
import { ValidationResult, ValidationSuccess } from "../util/ValidationResult.js";
5-
import { decodeOriginalScopes, decodeGeneratedRanges } from "tc39-proposal-scope-mapping";
5+
import { decodeOriginalScopes, decodeGeneratedRanges, getOriginalScopeChain, getGeneratedRangeChain } from "tc39-proposal-scope-mapping";
6+
import { SourceMapConsumer } from "source-map";
67
import type { SourceMap } from "../util/sourceMap.js";
78
import type { ValidationContext } from "../util/ValidationContext.js";
8-
import type { OriginalLocation, GeneratedRange, OriginalScope, ScopeKind } from "tc39-proposal-scope-mapping";
9+
import type { OriginalLocation, GeneratedRange, OriginalScope, ScopeKind, Location } from "tc39-proposal-scope-mapping";
910

1011
export class SourceMapScopesValidator extends Validator {
1112
private readonly scopeKindsWithName = new Set<ScopeKind>(["module", "function", "class"]);
1213

13-
validate({ sourceMap, originalFolderPath, generatedFilePath }: ValidationContext): ValidationResult {
14+
async validate({ sourceMap, originalFolderPath, generatedFilePath }: ValidationContext): Promise<ValidationResult> {
1415
if (!sourceMap.hasOwnProperty("originalScopes") && !sourceMap.hasOwnProperty("generatedRanges")) {
1516
return ValidationSuccess.create();
1617
}
@@ -62,6 +63,8 @@ export class SourceMapScopesValidator extends Validator {
6263

6364
this.validateGeneratedRange("generatedRange", decodedGeneratedRanges, errors, sourceMap, originalFiles, generatedFile);
6465

66+
await this.validateMappingConsistency(originalScopes, generatedRanges, errors, sourceMap);
67+
6568
return ValidationResult.from(errors);
6669
}
6770

@@ -106,6 +109,23 @@ export class SourceMapScopesValidator extends Validator {
106109
errors.push(new Error(`The scope (path) has a name property, but its kind is '${originalScope.kind}'. Only the next scope kinds are allowed to have the name property: ${Array.from(this.scopeKindsWithName).join(', ')}`));
107110
}
108111

112+
if (this.isLocationBefore(originalScope.end, originalScope.start)) {
113+
errors.push(new Error(`The original scope's end location is before its start location (${path})`));
114+
}
115+
if (originalScope.children?.length) {
116+
if (this.isLocationBefore(originalScope.children[0].start, originalScope.start)) {
117+
errors.push(new Error(`The original scope's start location is outside of its parent (${path})`));
118+
}
119+
if (this.isLocationBefore(originalScope.end, originalScope.children[originalScope.children.length - 1].end)) {
120+
errors.push(new Error(`The original scope's end location is outside of its parent (${path})`));
121+
}
122+
for (let i = 0; i < originalScope.children.length - 1; i++) {
123+
if (this.isLocationBefore(originalScope.children[i + 1].start, originalScope.children[i].end)) {
124+
errors.push(new Error(`The sibling original scopes overlap (${path})`));
125+
}
126+
}
127+
}
128+
109129
originalScope.children?.forEach((x, index) => {
110130
this.validateOriginalScope(`${path}.children[${index}]`, x, errors, sourceMap, originalFiles);
111131
})
@@ -170,11 +190,77 @@ export class SourceMapScopesValidator extends Validator {
170190
});
171191
}
172192

193+
if (this.isLocationBefore(generatedRange.end, generatedRange.start)) {
194+
errors.push(new Error(`The generated range's end location is before its start location (${path})`));
195+
}
196+
if (generatedRange.children?.length) {
197+
if (this.isLocationBefore(generatedRange.children[0].start, generatedRange.start)) {
198+
errors.push(new Error(`The generated range's start location is outside of its parent (${path})`));
199+
}
200+
if (this.isLocationBefore(generatedRange.end, generatedRange.children[generatedRange.children.length - 1].end)) {
201+
errors.push(new Error(`The generated range's end location is outside of its parent (${path})`));
202+
}
203+
for (let i = 0; i < generatedRange.children.length - 1; i++) {
204+
if (this.isLocationBefore(generatedRange.children[i + 1].start, generatedRange.children[i].end)) {
205+
errors.push(new Error(`The sibling original scopes overlap (${path})`));
206+
}
207+
}
208+
}
209+
173210
generatedRange.children?.forEach((x, index) => {
174211
this.validateGeneratedRange(`${path}.children[${index}]`, x, errors, sourceMap, originalFiles, generatedFile);
175212
})
176213
}
177214

215+
private async validateMappingConsistency(
216+
originalScopes: OriginalScope[],
217+
generatedRange: GeneratedRange,
218+
errors: Error[],
219+
sourceMap: SourceMap,
220+
) {
221+
try {
222+
await SourceMapConsumer.with(sourceMap, null, (consumer) => {
223+
consumer.eachMapping((mapping) => {
224+
const sourceIndex = sourceMap.sources.indexOf((source: string) => source === mapping.source);
225+
226+
const generatedRangeChain = getGeneratedRangeChain({
227+
line: mapping.generatedLine - 1,
228+
column: mapping.generatedColumn
229+
}, generatedRange);
230+
231+
let originalScopeChain = getOriginalScopeChain({
232+
line: mapping.originalLine - 1,
233+
column: mapping.originalColumn,
234+
sourceIndex
235+
}, originalScopes[sourceIndex]);
236+
237+
for (const generatedRange of generatedRangeChain) {
238+
if (!generatedRange.original) {
239+
continue;
240+
}
241+
242+
if (generatedRange.original.callsite) {
243+
originalScopeChain = getOriginalScopeChain(
244+
generatedRange.original.callsite,
245+
originalScopes[sourceIndex]
246+
);
247+
}
248+
249+
if (!originalScopeChain.includes(generatedRange.original.scope)) {
250+
errors.push(new Error(`OriginalScope reference is inconsistent with mappings`));
251+
}
252+
}
253+
});
254+
});
255+
} catch (exn : any) {
256+
errors.push(new Error(exn.message));
257+
}
258+
}
259+
260+
private isLocationBefore(a: Location, b: Location) {
261+
return a.line < b.line || (a.line === b.line && a.column < b.column);
262+
}
263+
178264
private formatWeirdOriginalScopeMessage(scopeLocation: OriginalLocation, originalFile: TestingFile): string {
179265
return `scope from "${originalFile.path} (${scopeLocation.line}:${scopeLocation.column})" looks not reasonable`;
180266
}

0 commit comments

Comments
 (0)