-
Notifications
You must be signed in to change notification settings - Fork 1
/
js_parser.go
17973 lines (15780 loc) · 614 KB
/
js_parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package js_parser
import (
"fmt"
"math"
"regexp"
"sort"
"strings"
"unicode/utf8"
"github.com/ije/esbuild-internal/ast"
"github.com/ije/esbuild-internal/compat"
"github.com/ije/esbuild-internal/config"
"github.com/ije/esbuild-internal/helpers"
"github.com/ije/esbuild-internal/js_ast"
"github.com/ije/esbuild-internal/js_lexer"
"github.com/ije/esbuild-internal/logger"
"github.com/ije/esbuild-internal/renamer"
"github.com/ije/esbuild-internal/runtime"
)
// This parser does two passes:
//
// 1. Parse the source into an AST, create the scope tree, and declare symbols.
//
// 2. Visit each node in the AST, bind identifiers to declared symbols, do
// constant folding, substitute compile-time variable definitions, and
// lower certain syntactic constructs as appropriate given the language
// target.
//
// So many things have been put in so few passes because we want to minimize
// the number of full-tree passes to improve performance. However, we need
// to have at least two separate passes to handle variable hoisting. See the
// comment about scopesInOrder below for more information.
type parser struct {
options Options
log logger.Log
source logger.Source
tracker logger.LineColumnTracker
fnOrArrowDataParse fnOrArrowDataParse
fnOnlyDataVisit fnOnlyDataVisit
allocatedNames []string
currentScope *js_ast.Scope
scopesForCurrentPart []*js_ast.Scope
symbols []ast.Symbol
astHelpers js_ast.HelperContext
tsUseCounts []uint32
injectedDefineSymbols []ast.Ref
injectedSymbolSources map[ast.Ref]injectedSymbolSource
injectedDotNames map[string][]injectedDotName
dropLabelsMap map[string]struct{}
exprComments map[logger.Loc][]string
mangledProps map[string]ast.Ref
reservedProps map[string]bool
symbolUses map[ast.Ref]js_ast.SymbolUse
importSymbolPropertyUses map[ast.Ref]map[string]js_ast.SymbolUse
symbolCallUses map[ast.Ref]js_ast.SymbolCallUse
declaredSymbols []js_ast.DeclaredSymbol
globPatternImports []globPatternImport
runtimeImports map[string]ast.LocRef
duplicateCaseChecker duplicateCaseChecker
unrepresentableIdentifiers map[string]bool
legacyOctalLiterals map[js_ast.E]logger.Range
scopesInOrderForEnum map[logger.Loc][]scopeOrder
binaryExprStack []binaryExprVisitor
// For strict mode handling
hoistedRefForSloppyModeBlockFn map[ast.Ref]ast.Ref
// For lowering private methods
privateGetters map[ast.Ref]ast.Ref
privateSetters map[ast.Ref]ast.Ref
// These are for TypeScript
//
// We build up enough information about the TypeScript namespace hierarchy to
// be able to resolve scope lookups and property accesses for TypeScript enum
// and namespace features. Each JavaScript scope object inside a namespace
// has a reference to a map of exported namespace members from sibling scopes.
//
// In addition, there is a map from each relevant symbol reference to the data
// associated with that namespace or namespace member: "refToTSNamespaceMemberData".
// This gives enough info to be able to resolve queries into the namespace.
//
// When visiting expressions, namespace metadata is associated with the most
// recently visited node. If namespace metadata is present, "tsNamespaceTarget"
// will be set to the most recently visited node (as a way to mark that this
// node has metadata) and "tsNamespaceMemberData" will be set to the metadata.
refToTSNamespaceMemberData map[ast.Ref]js_ast.TSNamespaceMemberData
tsNamespaceTarget js_ast.E
tsNamespaceMemberData js_ast.TSNamespaceMemberData
emittedNamespaceVars map[ast.Ref]bool
isExportedInsideNamespace map[ast.Ref]ast.Ref
localTypeNames map[string]bool
tsEnums map[ast.Ref]map[string]js_ast.TSEnumValue
constValues map[ast.Ref]js_ast.ConstValue
propDerivedCtorValue js_ast.E
propMethodDecoratorScope *js_ast.Scope
// This is the reference to the generated function argument for the namespace,
// which is different than the reference to the namespace itself:
//
// namespace ns {
// }
//
// The code above is transformed into something like this:
//
// var ns1;
// (function(ns2) {
// })(ns1 || (ns1 = {}));
//
// This variable is "ns2" not "ns1". It is only used during the second
// "visit" pass.
enclosingNamespaceArgRef *ast.Ref
// Imports (both ES6 and CommonJS) are tracked at the top level
importRecords []ast.ImportRecord
importRecordsForCurrentPart []uint32
exportStarImportRecords []uint32
// These are for handling ES6 imports and exports
importItemsForNamespace map[ast.Ref]namespaceImportItems
isImportItem map[ast.Ref]bool
namedImports map[ast.Ref]js_ast.NamedImport
namedExports map[string]js_ast.NamedExport
topLevelSymbolToParts map[ast.Ref][]uint32
importNamespaceCCMap map[importNamespaceCall]bool
// The parser does two passes and we need to pass the scope tree information
// from the first pass to the second pass. That's done by tracking the calls
// to pushScopeForParsePass() and popScope() during the first pass in
// scopesInOrder.
//
// Then, when the second pass calls pushScopeForVisitPass() and popScope(),
// we consume entries from scopesInOrder and make sure they are in the same
// order. This way the second pass can efficiently use the same scope tree
// as the first pass without having to attach the scope tree to the AST.
//
// We need to split this into two passes because the pass that declares the
// symbols must be separate from the pass that binds identifiers to declared
// symbols to handle declaring a hoisted "var" symbol in a nested scope and
// binding a name to it in a parent or sibling scope.
scopesInOrder []scopeOrder
// These propagate the name from the parent context into an anonymous child
// expression. For example:
//
// let foo = function() {}
// assert.strictEqual(foo.name, 'foo')
//
nameToKeep string
nameToKeepIsFor js_ast.E
// These properties are for the visit pass, which runs after the parse pass.
// The visit pass binds identifiers to declared symbols, does constant
// folding, substitutes compile-time variable definitions, and lowers certain
// syntactic constructs as appropriate.
stmtExprValue js_ast.E
callTarget js_ast.E
dotOrIndexTarget js_ast.E
templateTag js_ast.E
deleteTarget js_ast.E
loopBody js_ast.S
suspiciousLogicalOperatorInsideArrow js_ast.E
moduleScope *js_ast.Scope
// This is internal-only data used for the implementation of Yarn PnP
manifestForYarnPnP js_ast.Expr
stringLocalsForYarnPnP map[ast.Ref]stringLocalForYarnPnP
// This helps recognize the "await import()" pattern. When this is present,
// warnings about non-string import paths will be omitted inside try blocks.
awaitTarget js_ast.E
// This helps recognize the "import().catch()" pattern. We also try to avoid
// warning about this just like the "try { await import() }" pattern.
thenCatchChain thenCatchChain
// When bundling, hoisted top-level local variables declared with "var" in
// nested scopes are moved up to be declared in the top-level scope instead.
// The old "var" statements are turned into regular assignments instead. This
// makes it easier to quickly scan the top-level statements for "var" locals
// with the guarantee that all will be found.
relocatedTopLevelVars []ast.LocRef
// We need to lower private names such as "#foo" if they are used in a brand
// check such as "#foo in x" even if the private name syntax would otherwise
// be supported. This is because private names are a newly-added feature.
//
// However, this parser operates in only two passes for speed. The first pass
// parses things and declares variables, and the second pass lowers things and
// resolves references to declared variables. So the existence of a "#foo in x"
// expression for a specific "#foo" cannot be used to decide to lower "#foo"
// because it's too late by that point. There may be another expression such
// as "x.#foo" before that point and that must be lowered as well even though
// it has already been visited.
//
// Instead what we do is track just the names of fields used in private brand
// checks during the first pass. This tracks the names themselves, not symbol
// references. Then, during the second pass when we are about to enter into
// a class, we conservatively decide to lower all private names in that class
// which are used in a brand check anywhere in the file.
lowerAllOfThesePrivateNames map[string]bool
// Temporary variables used for lowering
tempLetsToDeclare []ast.Ref
tempRefsToDeclare []tempRef
topLevelTempRefsToDeclare []tempRef
lexer js_lexer.Lexer
// Private field access in a decorator lowers all private fields in that class
parseExperimentalDecoratorNesting int
// Temporary variables used for lowering
tempRefCount int
topLevelTempRefCount int
// We need to scan over the source contents to recover the line and column offsets
jsxSourceLoc int
jsxSourceLine int
jsxSourceColumn int
exportsRef ast.Ref
requireRef ast.Ref
moduleRef ast.Ref
importMetaRef ast.Ref
promiseRef ast.Ref
regExpRef ast.Ref
superCtorRef ast.Ref
// Imports from "react/jsx-runtime" and "react", respectively.
// (Or whatever was specified in the "importSource" option)
jsxRuntimeImports map[string]ast.LocRef
jsxLegacyImports map[string]ast.LocRef
// For lowering private methods
weakMapRef ast.Ref
weakSetRef ast.Ref
esmImportStatementKeyword logger.Range
esmImportMeta logger.Range
esmExportKeyword logger.Range
enclosingClassKeyword logger.Range
topLevelAwaitKeyword logger.Range
liveTopLevelAwaitKeyword logger.Range
latestArrowArgLoc logger.Loc
forbidSuffixAfterAsLoc logger.Loc
firstJSXElementLoc logger.Loc
fnOrArrowDataVisit fnOrArrowDataVisit
// ArrowFunction is a special case in the grammar. Although it appears to be
// a PrimaryExpression, it's actually an AssignmentExpression. This means if
// a AssignmentExpression ends up producing an ArrowFunction then nothing can
// come after it other than the comma operator, since the comma operator is
// the only thing above AssignmentExpression under the Expression rule:
//
// AssignmentExpression:
// ArrowFunction
// ConditionalExpression
// LeftHandSideExpression = AssignmentExpression
// LeftHandSideExpression AssignmentOperator AssignmentExpression
//
// Expression:
// AssignmentExpression
// Expression , AssignmentExpression
//
afterArrowBodyLoc logger.Loc
// Setting this to true disables warnings about code that is very likely to
// be a bug. This is used to ignore issues inside "node_modules" directories.
// This has caught real issues in the past. However, it's not esbuild's job
// to find bugs in other libraries, and these warnings are problematic for
// people using these libraries with esbuild. The only fix is to either
// disable all esbuild warnings and not get warnings about your own code, or
// to try to get the warning fixed in the affected library. This is
// especially annoying if the warning is a false positive as was the case in
// https://github.com/firebase/firebase-js-sdk/issues/3814. So these warnings
// are now disabled for code inside "node_modules" directories.
suppressWarningsAboutWeirdCode bool
// A file is considered to be an ECMAScript module if it has any of the
// features of one (e.g. the "export" keyword), otherwise it's considered
// a CommonJS module.
//
// However, we have a single exception: a file where the only ESM feature
// is the "import" keyword is allowed to have CommonJS exports. This feature
// is necessary to be able to synchronously import ESM code into CommonJS,
// which we need to enable in a few important cases. Some examples are:
// our runtime code, injected files (the "inject" feature is ESM-only),
// and certain automatically-generated virtual modules from plugins.
isFileConsideredToHaveESMExports bool // Use only for export-related stuff
isFileConsideredESM bool // Use for all other stuff
// Inside a TypeScript namespace, an "export declare" statement can be used
// to cause a namespace to be emitted even though it has no other observable
// effect. This flag is used to implement this feature.
//
// Specifically, namespaces should be generated for all of the following
// namespaces below except for "f", which should not be generated:
//
// namespace a { export declare const a }
// namespace b { export declare let [[b]] }
// namespace c { export declare function c() }
// namespace d { export declare class d {} }
// namespace e { export declare enum e {} }
// namespace f { export declare namespace f {} }
//
// The TypeScript compiler compiles this into the following code (notice "f"
// is missing):
//
// var a; (function (a_1) {})(a || (a = {}));
// var b; (function (b_1) {})(b || (b = {}));
// var c; (function (c_1) {})(c || (c = {}));
// var d; (function (d_1) {})(d || (d = {}));
// var e; (function (e_1) {})(e || (e = {}));
//
// Note that this should not be implemented by declaring symbols for "export
// declare" statements because the TypeScript compiler doesn't generate any
// code for these statements, so these statements are actually references to
// global variables. There is one exception, which is that local variables
// *should* be declared as symbols because they are replaced with. This seems
// like very arbitrary behavior but it's what the TypeScript compiler does,
// so we try to match it.
//
// Specifically, in the following code below "a" and "b" should be declared
// and should be substituted with "ns.a" and "ns.b" but the other symbols
// shouldn't. References to the other symbols actually refer to global
// variables instead of to symbols that are exported from the namespace.
// This is the case as of TypeScript 4.3. I assume this is a TypeScript bug:
//
// namespace ns {
// export declare const a
// export declare let [[b]]
// export declare function c()
// export declare class d { }
// export declare enum e { }
// console.log(a, b, c, d, e)
// }
//
// The TypeScript compiler compiles this into the following code:
//
// var ns;
// (function (ns) {
// console.log(ns.a, ns.b, c, d, e);
// })(ns || (ns = {}));
//
// Relevant issue: https://github.com/evanw/esbuild/issues/1158
hasNonLocalExportDeclareInsideNamespace bool
// When this flag is enabled, we attempt to fold all expressions that
// TypeScript would consider to be "constant expressions". This flag is
// enabled inside each enum body block since TypeScript requires numeric
// constant folding in enum definitions.
//
// We also enable this flag in certain cases in JavaScript files such as when
// parsing "const" declarations at the top of a non-ESM file, but we still
// reuse TypeScript's notion of "constant expressions" for our own convenience.
//
// As of TypeScript 5.0, a "constant expression" is defined as follows:
//
// An expression is considered a constant expression if it is
//
// * a number or string literal,
// * a unary +, -, or ~ applied to a numeric constant expression,
// * a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
// * a binary + applied to two constant expressions whereof at least one is a string,
// * a template expression where each substitution expression is a constant expression,
// * a parenthesized constant expression,
// * a dotted name (e.g. x.y.z) that references a const variable with a constant expression initializer and no type annotation,
// * a dotted name that references an enum member with an enum literal type, or
// * a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type.
//
// More detail: https://github.com/microsoft/TypeScript/pull/50528. Note that
// we don't implement certain items in this list. For example, we don't do all
// number-to-string conversions since ours might differ from how JavaScript
// would do it, which would be a correctness issue.
shouldFoldTypeScriptConstantExpressions bool
allowIn bool
allowPrivateIdentifiers bool
hasTopLevelReturn bool
latestReturnHadSemicolon bool
messageAboutThisIsUndefined bool
isControlFlowDead bool
// If this is true, then all top-level statements are wrapped in a try/catch
willWrapModuleInTryCatchForUsing bool
}
type globPatternImport struct {
assertOrWith *ast.ImportAssertOrWith
parts []helpers.GlobPart
name string
approximateRange logger.Range
ref ast.Ref
kind ast.ImportKind
}
type namespaceImportItems struct {
entries map[string]ast.LocRef
importRecordIndex uint32
}
type stringLocalForYarnPnP struct {
value []uint16
loc logger.Loc
}
type injectedSymbolSource struct {
source logger.Source
loc logger.Loc
}
type injectedDotName struct {
parts []string
injectedDefineIndex uint32
}
type importNamespaceCallKind uint8
const (
exprKindCall importNamespaceCallKind = iota
exprKindNew
exprKindJSXTag
)
type importNamespaceCall struct {
ref ast.Ref
kind importNamespaceCallKind
}
type thenCatchChain struct {
nextTarget js_ast.E
catchLoc logger.Loc
hasMultipleArgs bool
hasCatch bool
}
// This is used as part of an incremental build cache key. Some of these values
// can potentially change between builds if they are derived from nearby
// "package.json" or "tsconfig.json" files that were changed since the last
// build.
type Options struct {
injectedFiles []config.InjectedFile
jsx config.JSXOptions
tsAlwaysStrict *config.TSAlwaysStrict
mangleProps *regexp.Regexp
reserveProps *regexp.Regexp
dropLabels []string
// This pointer will always be different for each build but the contents
// shouldn't ever behave different semantically. We ignore this field for the
// equality comparison.
defines *config.ProcessedDefines
// This is an embedded struct. Always access these directly instead of off
// the name "optionsThatSupportStructuralEquality". This is only grouped like
// this to make the equality comparison easier and safer (and hopefully faster).
optionsThatSupportStructuralEquality
}
type optionsThatSupportStructuralEquality struct {
originalTargetEnv string
moduleTypeData js_ast.ModuleTypeData
unsupportedJSFeatures compat.JSFeature
unsupportedJSFeatureOverrides compat.JSFeature
unsupportedJSFeatureOverridesMask compat.JSFeature
// Byte-sized values go here (gathered together here to keep this object compact)
ts config.TSOptions
mode config.Mode
platform config.Platform
outputFormat config.Format
asciiOnly bool
keepNames bool
minifySyntax bool
minifyIdentifiers bool
minifyWhitespace bool
omitRuntimeForTests bool
omitJSXRuntimeForTests bool
ignoreDCEAnnotations bool
treeShaking bool
dropDebugger bool
mangleQuoted bool
// This is an internal-only option used for the implementation of Yarn PnP
decodeHydrateRuntimeStateYarnPnP bool
}
func OptionsForYarnPnP() Options {
return Options{
optionsThatSupportStructuralEquality: optionsThatSupportStructuralEquality{
decodeHydrateRuntimeStateYarnPnP: true,
},
}
}
func OptionsFromConfig(options *config.Options) Options {
return Options{
injectedFiles: options.InjectedFiles,
jsx: options.JSX,
defines: options.Defines,
tsAlwaysStrict: options.TSAlwaysStrict,
mangleProps: options.MangleProps,
reserveProps: options.ReserveProps,
dropLabels: options.DropLabels,
optionsThatSupportStructuralEquality: optionsThatSupportStructuralEquality{
unsupportedJSFeatures: options.UnsupportedJSFeatures,
unsupportedJSFeatureOverrides: options.UnsupportedJSFeatureOverrides,
unsupportedJSFeatureOverridesMask: options.UnsupportedJSFeatureOverridesMask,
originalTargetEnv: options.OriginalTargetEnv,
ts: options.TS,
mode: options.Mode,
platform: options.Platform,
outputFormat: options.OutputFormat,
moduleTypeData: options.ModuleTypeData,
asciiOnly: options.ASCIIOnly,
keepNames: options.KeepNames,
minifySyntax: options.MinifySyntax,
minifyIdentifiers: options.MinifyIdentifiers,
minifyWhitespace: options.MinifyWhitespace,
omitRuntimeForTests: options.OmitRuntimeForTests,
omitJSXRuntimeForTests: options.OmitJSXRuntimeForTests,
ignoreDCEAnnotations: options.IgnoreDCEAnnotations,
treeShaking: options.TreeShaking,
dropDebugger: options.DropDebugger,
mangleQuoted: options.MangleQuoted,
},
}
}
func (a *Options) Equal(b *Options) bool {
// Compare "optionsThatSupportStructuralEquality"
if a.optionsThatSupportStructuralEquality != b.optionsThatSupportStructuralEquality {
return false
}
// Compare "tsAlwaysStrict"
if (a.tsAlwaysStrict == nil && b.tsAlwaysStrict != nil) || (a.tsAlwaysStrict != nil && b.tsAlwaysStrict == nil) ||
(a.tsAlwaysStrict != nil && b.tsAlwaysStrict != nil && *a.tsAlwaysStrict != *b.tsAlwaysStrict) {
return false
}
// Compare "mangleProps" and "reserveProps"
if !isSameRegexp(a.mangleProps, b.mangleProps) || !isSameRegexp(a.reserveProps, b.reserveProps) {
return false
}
// Compare "dropLabels"
if !helpers.StringArraysEqual(a.dropLabels, b.dropLabels) {
return false
}
// Compare "injectedFiles"
if len(a.injectedFiles) != len(b.injectedFiles) {
return false
}
for i, x := range a.injectedFiles {
y := b.injectedFiles[i]
if x.Source != y.Source || x.DefineName != y.DefineName || len(x.Exports) != len(y.Exports) {
return false
}
for j := range x.Exports {
if x.Exports[j] != y.Exports[j] {
return false
}
}
}
// Compare "jsx"
if a.jsx.Parse != b.jsx.Parse || !jsxExprsEqual(a.jsx.Factory, b.jsx.Factory) || !jsxExprsEqual(a.jsx.Fragment, b.jsx.Fragment) {
return false
}
// Do a cheap assert that the defines object hasn't changed
if (a.defines != nil || b.defines != nil) && (a.defines == nil || b.defines == nil ||
len(a.defines.IdentifierDefines) != len(b.defines.IdentifierDefines) ||
len(a.defines.DotDefines) != len(b.defines.DotDefines)) {
panic("Internal error")
}
return true
}
func isSameRegexp(a *regexp.Regexp, b *regexp.Regexp) bool {
if a == nil {
return b == nil
} else {
return b != nil && a.String() == b.String()
}
}
func jsxExprsEqual(a config.DefineExpr, b config.DefineExpr) bool {
if !helpers.StringArraysEqual(a.Parts, b.Parts) {
return false
}
if a.Constant != nil {
if b.Constant == nil || !js_ast.ValuesLookTheSame(a.Constant, b.Constant) {
return false
}
} else if b.Constant != nil {
return false
}
return true
}
type tempRef struct {
valueOrNil js_ast.Expr
ref ast.Ref
}
const (
locModuleScope = -1
)
type scopeOrder struct {
scope *js_ast.Scope
loc logger.Loc
}
type awaitOrYield uint8
const (
// The keyword is used as an identifier, not a special expression
allowIdent awaitOrYield = iota
// Declaring the identifier is forbidden, and the keyword is used as a special expression
allowExpr
// Declaring the identifier is forbidden, and using the identifier is also forbidden
forbidAll
)
// This is function-specific information used during parsing. It is saved and
// restored on the call stack around code that parses nested functions and
// arrow expressions.
type fnOrArrowDataParse struct {
arrowArgErrors *deferredArrowArgErrors
decoratorScope *js_ast.Scope
asyncRange logger.Range
needsAsyncLoc logger.Loc
await awaitOrYield
yield awaitOrYield
allowSuperCall bool
allowSuperProperty bool
isTopLevel bool
isConstructor bool
isTypeScriptDeclare bool
isThisDisallowed bool
isReturnDisallowed bool
// In TypeScript, forward declarations of functions have no bodies
allowMissingBodyForTypeScript bool
}
// This is function-specific information used during visiting. It is saved and
// restored on the call stack around code that parses nested functions and
// arrow expressions.
type fnOrArrowDataVisit struct {
// This is used to silence unresolvable imports due to "require" calls inside
// a try/catch statement. The assumption is that the try/catch statement is
// there to handle the case where the reference to "require" crashes.
tryBodyCount int32
tryCatchLoc logger.Loc
isArrow bool
isAsync bool
isGenerator bool
isInsideLoop bool
isInsideSwitch bool
isDerivedClassCtor bool
isOutsideFnOrArrow bool
shouldLowerSuperPropertyAccess bool
}
// This is function-specific information used during visiting. It is saved and
// restored on the call stack around code that parses nested functions (but not
// nested arrow functions).
type fnOnlyDataVisit struct {
// This is a reference to the magic "arguments" variable that exists inside
// functions in JavaScript. It will be non-nil inside functions and nil
// otherwise.
argumentsRef *ast.Ref
// Arrow functions don't capture the value of "this" and "arguments". Instead,
// the values are inherited from the surrounding context. If arrow functions
// are turned into regular functions due to lowering, we will need to generate
// local variables to capture these values so they are preserved correctly.
thisCaptureRef *ast.Ref
argumentsCaptureRef *ast.Ref
// If true, we're inside a static class context where "this" expressions
// should be replaced with the class name.
shouldReplaceThisWithInnerClassNameRef bool
// This is true if "this" is equal to the class name. It's true if we're in a
// static class field initializer, a static class method, or a static class
// block.
isInStaticClassContext bool
// This is a reference to the enclosing class name if there is one. It's used
// to implement "this" and "super" references. A name is automatically generated
// if one is missing so this will always be present inside a class body.
innerClassNameRef *ast.Ref
// If we're inside an async arrow function and async functions are not
// supported, then we will have to convert that arrow function to a generator
// function. That means references to "arguments" inside the arrow function
// will have to reference a captured variable instead of the real variable.
isInsideAsyncArrowFn bool
// If false, disallow "new.target" expressions. We disallow all "new.target"
// expressions at the top-level of the file (i.e. not inside a function or
// a class field). Technically since CommonJS files are wrapped in a function
// you can use "new.target" in node as an alias for "undefined" but we don't
// support that.
isNewTargetAllowed bool
// If false, the value for "this" is the top-level module scope "this" value.
// That means it's "undefined" for ECMAScript modules and "exports" for
// CommonJS modules. We track this information so that we can substitute the
// correct value for these top-level "this" references at compile time instead
// of passing the "this" expression through to the output and leaving the
// interpretation up to the run-time behavior of the generated code.
//
// If true, the value for "this" is nested inside something (either a function
// or a class declaration). That means the top-level module scope "this" value
// has been shadowed and is now inaccessible.
isThisNested bool
// Do not warn about "this" being undefined for code that the TypeScript
// compiler generates that looks like this:
//
// var __rest = (this && this.__rest) || function (s, e) {
// ...
// };
//
silenceMessageAboutThisBeingUndefined bool
}
const bloomFilterSize = 251
type duplicateCaseValue struct {
value js_ast.Expr
hash uint32
}
type duplicateCaseChecker struct {
cases []duplicateCaseValue
bloomFilter [(bloomFilterSize + 7) / 8]byte
}
func (dc *duplicateCaseChecker) reset() {
// Preserve capacity
dc.cases = dc.cases[:0]
// This should be optimized by the compiler. See this for more information:
// https://github.com/golang/go/issues/5373
bytes := dc.bloomFilter
for i := range bytes {
bytes[i] = 0
}
}
func (dc *duplicateCaseChecker) check(p *parser, expr js_ast.Expr) {
if hash, ok := duplicateCaseHash(expr); ok {
bucket := hash % bloomFilterSize
entry := &dc.bloomFilter[bucket/8]
mask := byte(1) << (bucket % 8)
// Check for collisions
if (*entry & mask) != 0 {
for _, c := range dc.cases {
if c.hash == hash {
if equals, couldBeIncorrect := duplicateCaseEquals(c.value, expr); equals {
var laterRange logger.Range
var earlierRange logger.Range
if _, ok := expr.Data.(*js_ast.EString); ok {
laterRange = p.source.RangeOfString(expr.Loc)
} else {
laterRange = p.source.RangeOfOperatorBefore(expr.Loc, "case")
}
if _, ok := c.value.Data.(*js_ast.EString); ok {
earlierRange = p.source.RangeOfString(c.value.Loc)
} else {
earlierRange = p.source.RangeOfOperatorBefore(c.value.Loc, "case")
}
text := "This case clause will never be evaluated because it duplicates an earlier case clause"
if couldBeIncorrect {
text = "This case clause may never be evaluated because it likely duplicates an earlier case clause"
}
kind := logger.Warning
if p.suppressWarningsAboutWeirdCode {
kind = logger.Debug
}
p.log.AddIDWithNotes(logger.MsgID_JS_DuplicateCase, kind, &p.tracker, laterRange, text,
[]logger.MsgData{p.tracker.MsgData(earlierRange, "The earlier case clause is here:")})
}
return
}
}
}
*entry |= mask
dc.cases = append(dc.cases, duplicateCaseValue{hash: hash, value: expr})
}
}
func duplicateCaseHash(expr js_ast.Expr) (uint32, bool) {
switch e := expr.Data.(type) {
case *js_ast.EInlinedEnum:
return duplicateCaseHash(e.Value)
case *js_ast.ENull:
return 0, true
case *js_ast.EUndefined:
return 1, true
case *js_ast.EBoolean:
if e.Value {
return helpers.HashCombine(2, 1), true
}
return helpers.HashCombine(2, 0), true
case *js_ast.ENumber:
bits := math.Float64bits(e.Value)
return helpers.HashCombine(helpers.HashCombine(3, uint32(bits)), uint32(bits>>32)), true
case *js_ast.EString:
hash := uint32(4)
for _, c := range e.Value {
hash = helpers.HashCombine(hash, uint32(c))
}
return hash, true
case *js_ast.EBigInt:
hash := uint32(5)
for _, c := range e.Value {
hash = helpers.HashCombine(hash, uint32(c))
}
return hash, true
case *js_ast.EIdentifier:
return helpers.HashCombine(6, e.Ref.InnerIndex), true
case *js_ast.EDot:
if target, ok := duplicateCaseHash(e.Target); ok {
return helpers.HashCombineString(helpers.HashCombine(7, target), e.Name), true
}
case *js_ast.EIndex:
if target, ok := duplicateCaseHash(e.Target); ok {
if index, ok := duplicateCaseHash(e.Index); ok {
return helpers.HashCombine(helpers.HashCombine(8, target), index), true
}
}
}
return 0, false
}
func duplicateCaseEquals(left js_ast.Expr, right js_ast.Expr) (equals bool, couldBeIncorrect bool) {
if b, ok := right.Data.(*js_ast.EInlinedEnum); ok {
return duplicateCaseEquals(left, b.Value)
}
switch a := left.Data.(type) {
case *js_ast.EInlinedEnum:
return duplicateCaseEquals(a.Value, right)
case *js_ast.ENull:
_, ok := right.Data.(*js_ast.ENull)
return ok, false
case *js_ast.EUndefined:
_, ok := right.Data.(*js_ast.EUndefined)
return ok, false
case *js_ast.EBoolean:
b, ok := right.Data.(*js_ast.EBoolean)
return ok && a.Value == b.Value, false
case *js_ast.ENumber:
b, ok := right.Data.(*js_ast.ENumber)
return ok && a.Value == b.Value, false
case *js_ast.EString:
b, ok := right.Data.(*js_ast.EString)
return ok && helpers.UTF16EqualsUTF16(a.Value, b.Value), false
case *js_ast.EBigInt:
if b, ok := right.Data.(*js_ast.EBigInt); ok {
equal, ok := js_ast.CheckEqualityBigInt(a.Value, b.Value)
return ok && equal, false
}
case *js_ast.EIdentifier:
b, ok := right.Data.(*js_ast.EIdentifier)
return ok && a.Ref == b.Ref, false
case *js_ast.EDot:
if b, ok := right.Data.(*js_ast.EDot); ok && a.OptionalChain == b.OptionalChain && a.Name == b.Name {
equals, _ := duplicateCaseEquals(a.Target, b.Target)
return equals, true
}
case *js_ast.EIndex:
if b, ok := right.Data.(*js_ast.EIndex); ok && a.OptionalChain == b.OptionalChain {
if equals, _ := duplicateCaseEquals(a.Index, b.Index); equals {
equals, _ := duplicateCaseEquals(a.Target, b.Target)
return equals, true
}
}
}
return false, false
}
type duplicatePropertiesIn uint8
const (
duplicatePropertiesInObject duplicatePropertiesIn = iota
duplicatePropertiesInClass
)
func (p *parser) warnAboutDuplicateProperties(properties []js_ast.Property, in duplicatePropertiesIn) {
if len(properties) < 2 {
return
}
type keyKind uint8
type existingKey struct {
loc logger.Loc
kind keyKind
}
const (
keyMissing keyKind = iota
keyNormal
keyGet
keySet
keyGetAndSet
)
instanceKeys := make(map[string]existingKey)
staticKeys := make(map[string]existingKey)
for _, property := range properties {
if property.Kind != js_ast.PropertySpread {
if str, ok := property.Key.Data.(*js_ast.EString); ok {
var keys map[string]existingKey
if property.Flags.Has(js_ast.PropertyIsStatic) {
keys = staticKeys
} else {
keys = instanceKeys
}
key := helpers.UTF16ToString(str.Value)
prevKey := keys[key]
nextKey := existingKey{kind: keyNormal, loc: property.Key.Loc}
if property.Kind == js_ast.PropertyGetter {
nextKey.kind = keyGet
} else if property.Kind == js_ast.PropertySetter {
nextKey.kind = keySet
}
if prevKey.kind != keyMissing && (in != duplicatePropertiesInObject || key != "__proto__") && (in != duplicatePropertiesInClass || key != "constructor") {
if (prevKey.kind == keyGet && nextKey.kind == keySet) || (prevKey.kind == keySet && nextKey.kind == keyGet) {
nextKey.kind = keyGetAndSet
} else {
var id logger.MsgID
var what string
var where string
switch in {
case duplicatePropertiesInObject:
id = logger.MsgID_JS_DuplicateObjectKey
what = "key"
where = "object literal"
case duplicatePropertiesInClass:
id = logger.MsgID_JS_DuplicateClassMember
what = "member"
where = "class body"
}
r := js_lexer.RangeOfIdentifier(p.source, property.Key.Loc)
p.log.AddIDWithNotes(id, logger.Warning, &p.tracker, r,
fmt.Sprintf("Duplicate %s %q in %s", what, key, where),
[]logger.MsgData{p.tracker.MsgData(js_lexer.RangeOfIdentifier(p.source, prevKey.loc),
fmt.Sprintf("The original %s %q is here:", what, key))})
}
}
keys[key] = nextKey
}
}
}