19
19
import static com .google .common .base .Preconditions .checkArgument ;
20
20
import static com .google .common .base .Preconditions .checkNotNull ;
21
21
import static com .google .common .base .Preconditions .checkState ;
22
+ import static com .google .javascript .jscomp .base .JSCompObjects .identical ;
22
23
23
24
import com .google .common .base .Preconditions ;
24
25
import com .google .common .collect .ImmutableCollection ;
30
31
import com .google .javascript .jscomp .deps .ModuleLoader ;
31
32
import com .google .javascript .jscomp .deps .ModuleLoader .ModulePath ;
32
33
import com .google .javascript .jscomp .deps .SimpleDependencyInfo ;
34
+ import com .google .javascript .jscomp .parsing .ParserRunner ;
33
35
import com .google .javascript .jscomp .parsing .parser .FeatureSet ;
36
+ import com .google .javascript .rhino .IR ;
34
37
import com .google .javascript .rhino .InputId ;
35
38
import com .google .javascript .rhino .JSDocInfo ;
36
39
import com .google .javascript .rhino .Node ;
40
43
import java .util .List ;
41
44
import java .util .Map ;
42
45
import java .util .TreeMap ;
46
+ import java .util .function .Supplier ;
43
47
import org .jspecify .annotations .Nullable ;
44
48
45
49
/**
@@ -54,9 +58,10 @@ public class CompilerInput implements DependencyInfo {
54
58
// Info about where the file lives.
55
59
private JSChunk chunk ;
56
60
private final InputId id ;
61
+ private final SourceFile sourceFile ;
57
62
58
- // The AST.
59
- private final SourceAst ast ;
63
+ // The lazily constructed AST.
64
+ private final JsAst ast = new JsAst () ;
60
65
61
66
// DependencyInfo to delegate to.
62
67
private DependencyInfo dependencyInfo ;
@@ -98,46 +103,36 @@ public void setTypedScope(@Nullable TypedScope typedScope) {
98
103
// with the AST node. Once (when?) we enforce that extern files always contain an @externs
99
104
// annotation, we can store the extern bit in the AST node, and make SourceFile immutable.
100
105
101
- public CompilerInput (SourceAst ast ) {
102
- this (ast , ast .getSourceFile ().getName (), false );
103
- }
104
-
105
- public CompilerInput (SourceAst ast , boolean isExtern ) {
106
- this .ast = ast ;
107
- this .id = ast .getInputId ();
108
-
109
- if (isExtern ) {
110
- setIsExtern ();
111
- }
106
+ public CompilerInput (SourceFile file , InputId inputId ) {
107
+ this (file , inputId , false );
112
108
}
113
109
114
110
/**
115
111
* @deprecated the inputId is read from the SourceAst. Use CompilerInput(ast, isExtern)
116
112
*/
117
113
@ Deprecated
118
- public CompilerInput (SourceAst ast , String inputId , boolean isExtern ) {
119
- this (ast , new InputId (inputId ), isExtern );
114
+ public CompilerInput (SourceFile sourceFile , String inputId , boolean isExtern ) {
115
+ this (sourceFile , new InputId (inputId ), isExtern );
120
116
}
121
117
122
118
/**
123
119
* @deprecated the inputId is read from the SourceAst. Use CompilerInput(ast, isExtern)
124
120
*/
125
121
@ Deprecated
126
- public CompilerInput (SourceAst ast , InputId inputId , boolean isExtern ) {
127
- this (ast , isExtern );
128
- checkArgument (
129
- inputId .equals (ast .getInputId ()),
130
- "mismatched input ids\n ctor param: %s\n ast.getInputId(): %s" ,
131
- inputId ,
132
- ast .getInputId ());
122
+ public CompilerInput (SourceFile sourceFile , InputId inputId , boolean isExtern ) {
123
+ this .sourceFile = sourceFile ;
124
+ this .id = inputId ;
125
+ if (isExtern ) {
126
+ setIsExtern ();
127
+ }
133
128
}
134
129
135
130
public CompilerInput (SourceFile file ) {
136
131
this (file , false );
137
132
}
138
133
139
134
public CompilerInput (SourceFile file , boolean isExtern ) {
140
- this (new JsAst (file ), isExtern );
135
+ this (file , new InputId (file . getName () ), isExtern );
141
136
}
142
137
143
138
/** Returns a name for this input. Must be unique across all inputs. */
@@ -170,7 +165,7 @@ public void clearAst() {
170
165
}
171
166
172
167
public SourceFile getSourceFile () {
173
- return ast . getSourceFile () ;
168
+ return sourceFile ;
174
169
}
175
170
176
171
/** Sets an abstract compiler for doing parsing. */
@@ -341,7 +336,7 @@ private DependencyInfo generateDependencyInfo() {
341
336
342
337
// If the code is a JsAst, then it was originally JS code, and is compatible with the
343
338
// regex-based parsing of JsFileRegexParser.
344
- if (ast instanceof JsAst && JsFileRegexParser .isSupported () && compiler .preferRegexParser ()) {
339
+ if (JsFileRegexParser .isSupported () && compiler .preferRegexParser ()) {
345
340
// Look at the source code.
346
341
// Note: it's OK to use getName() instead of
347
342
// getPathRelativeToClosureBase() here because we're not using
@@ -353,7 +348,7 @@ private DependencyInfo generateDependencyInfo() {
353
348
.setModuleLoader (compiler .getModuleLoader ())
354
349
.setIncludeGoogBase (true )
355
350
.parseFile (getName (), getName (), getCode ());
356
- return new LazyParsedDependencyInfo (info , ( JsAst ) ast , compiler );
351
+ return new LazyParsedDependencyInfo (info , this , compiler );
357
352
} catch (IOException e ) {
358
353
compiler
359
354
.getErrorManager ()
@@ -573,22 +568,22 @@ void overrideModule(JSChunk chunk) {
573
568
}
574
569
575
570
public boolean isExtern () {
576
- if (ast == null || ast . getSourceFile () == null ) {
571
+ if (sourceFile == null ) {
577
572
return false ;
578
573
}
579
- return ast . getSourceFile () .isExtern ();
574
+ return sourceFile .isExtern ();
580
575
}
581
576
582
577
void setIsExtern () {
583
578
// TODO(tjgq): Add a precondition check here. People are passing in null, but they shouldn't be.
584
- if (ast == null || ast . getSourceFile () == null ) {
579
+ if (sourceFile == null ) {
585
580
return ;
586
581
}
587
- ast . getSourceFile () .setKind (SourceKind .EXTERN );
582
+ sourceFile .setKind (SourceKind .EXTERN );
588
583
}
589
584
590
585
public int getLineOffset (int lineno ) {
591
- return ast . getSourceFile () .getLineOffset (lineno );
586
+ return sourceFile .getLineOffset (lineno );
592
587
}
593
588
594
589
@ Override
@@ -636,4 +631,96 @@ public enum ModuleType {
636
631
JSON ,
637
632
IMPORTED_SCRIPT
638
633
}
634
+
635
+ public FeatureSet getFeatures (AbstractCompiler compiler ) {
636
+ var unused = this .ast .getAstRoot (compiler ); // parse if required
637
+ return this .ast .features ;
638
+ }
639
+
640
+ /**
641
+ * Wraps the Rhino AST for this CompilerInput.
642
+ *
643
+ * <p>Use this wrapper class instead of directly storing a Node because the AST is lazily
644
+ * constructed. CompilerInputs are created before the parsing step, and in some cases the AST for
645
+ * a given CompilerInput may never be parsed if dependency pruning is enabled.
646
+ *
647
+ * <p>Additionally if using precompiled libraries, this class handles deserializing the AST from
648
+ * the TypedAST format, instead of parsing it from the source.
649
+ */
650
+ private final class JsAst {
651
+ private @ Nullable Node root ;
652
+ private FeatureSet features ;
653
+
654
+ public Node getAstRoot (AbstractCompiler compiler ) {
655
+ if (this .isParsed ()) {
656
+ return this .root ;
657
+ }
658
+
659
+ Supplier <Node > astRootSource = compiler .getTypedAstDeserializer (sourceFile );
660
+ if (astRootSource != null ) {
661
+ this .root = astRootSource .get ();
662
+ this .features = (FeatureSet ) this .root .getProp (Node .FEATURE_SET );
663
+ } else {
664
+ this .parse (compiler );
665
+ }
666
+ checkState (identical (this .root .getStaticSourceFile (), sourceFile ));
667
+ this .root .setInputId (id );
668
+ // Clear the cached source after parsing. It will be re-read for snippet generation if
669
+ // needed.
670
+ sourceFile .clearCachedSource ();
671
+ return this .root ;
672
+ }
673
+
674
+ public void clearAst () {
675
+ root = null ;
676
+ // While we're at it, clear out any saved text in the source file on
677
+ // the assumption that if we're dumping the parse tree, then we probably
678
+ // assume regenerating everything else is a smart idea also.
679
+ sourceFile .clearCachedSource ();
680
+ }
681
+
682
+ private boolean isParsed () {
683
+ return root != null ;
684
+ }
685
+
686
+ private void parse (AbstractCompiler compiler ) {
687
+ try {
688
+ ParserRunner .ParseResult result =
689
+ ParserRunner .parse (
690
+ sourceFile ,
691
+ sourceFile .getCode (),
692
+ compiler .getParserConfig (
693
+ sourceFile .isExtern ()
694
+ ? AbstractCompiler .ConfigContext .EXTERNS
695
+ : AbstractCompiler .ConfigContext .DEFAULT ),
696
+ compiler .getDefaultErrorReporter ());
697
+ root = result .ast ;
698
+ features = result .features ;
699
+
700
+ if (compiler .getOptions ().preservesDetailedSourceInfo ()) {
701
+ compiler .addComments (sourceFile .getName (), result .comments );
702
+ }
703
+ if (result .sourceMapURL != null && compiler .getOptions ().resolveSourceMapAnnotations ) {
704
+ boolean parseInline = compiler .getOptions ().parseInlineSourceMaps ;
705
+ SourceFile sourceMapSourceFile =
706
+ SourceMapResolver .extractSourceMap (sourceFile , result .sourceMapURL , parseInline );
707
+ if (sourceMapSourceFile != null ) {
708
+ compiler .addInputSourceMap (
709
+ sourceFile .getName (), new SourceMapInput (sourceMapSourceFile ));
710
+ }
711
+ }
712
+ } catch (IOException e ) {
713
+ compiler .report (
714
+ JSError .make (AbstractCompiler .READ_ERROR , sourceFile .getName (), e .getMessage ()));
715
+ }
716
+
717
+ if (root == null ) {
718
+ root = IR .script ();
719
+ }
720
+
721
+ // Set the source name so that the compiler passes can track
722
+ // the source file and module.
723
+ root .setStaticSourceFile (sourceFile );
724
+ }
725
+ }
639
726
}
0 commit comments