@@ -2,15 +2,16 @@ import { Validator } from "../util/Validator.js";
2
2
import { TestingFile } from "../util/TestingFile.js" ;
3
3
import { collectSourceFiles } from "../util/collectSourceFiles.js" ;
4
4
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" ;
6
7
import type { SourceMap } from "../util/sourceMap.js" ;
7
8
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" ;
9
10
10
11
export class SourceMapScopesValidator extends Validator {
11
12
private readonly scopeKindsWithName = new Set < ScopeKind > ( [ "module" , "function" , "class" ] ) ;
12
13
13
- validate ( { sourceMap, originalFolderPath, generatedFilePath } : ValidationContext ) : ValidationResult {
14
+ async validate ( { sourceMap, originalFolderPath, generatedFilePath } : ValidationContext ) : Promise < ValidationResult > {
14
15
if ( ! sourceMap . hasOwnProperty ( "originalScopes" ) && ! sourceMap . hasOwnProperty ( "generatedRanges" ) ) {
15
16
return ValidationSuccess . create ( ) ;
16
17
}
@@ -62,6 +63,8 @@ export class SourceMapScopesValidator extends Validator {
62
63
63
64
this . validateGeneratedRange ( "generatedRange" , decodedGeneratedRanges , errors , sourceMap , originalFiles , generatedFile ) ;
64
65
66
+ await this . validateMappingConsistency ( originalScopes , generatedRanges , errors , sourceMap ) ;
67
+
65
68
return ValidationResult . from ( errors ) ;
66
69
}
67
70
@@ -106,6 +109,23 @@ export class SourceMapScopesValidator extends Validator {
106
109
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 ( ', ' ) } ` ) ) ;
107
110
}
108
111
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
+
109
129
originalScope . children ?. forEach ( ( x , index ) => {
110
130
this . validateOriginalScope ( `${ path } .children[${ index } ]` , x , errors , sourceMap , originalFiles ) ;
111
131
} )
@@ -170,11 +190,77 @@ export class SourceMapScopesValidator extends Validator {
170
190
} ) ;
171
191
}
172
192
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
+
173
210
generatedRange . children ?. forEach ( ( x , index ) => {
174
211
this . validateGeneratedRange ( `${ path } .children[${ index } ]` , x , errors , sourceMap , originalFiles , generatedFile ) ;
175
212
} )
176
213
}
177
214
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
+
178
264
private formatWeirdOriginalScopeMessage ( scopeLocation : OriginalLocation , originalFile : TestingFile ) : string {
179
265
return `scope from "${ originalFile . path } (${ scopeLocation . line } :${ scopeLocation . column } )" looks not reasonable` ;
180
266
}
0 commit comments