140
140
import com .google .javascript .rhino .dtoa .DToA ;
141
141
import java .math .BigInteger ;
142
142
import java .util .ArrayDeque ;
143
+ import java .util .Comparator ;
143
144
import java .util .Deque ;
144
145
import java .util .LinkedHashSet ;
145
146
import java .util .List ;
@@ -293,14 +294,7 @@ private IRFactory(
293
294
this .sourceName = sourceFile == null ? null : sourceFile .getName ();
294
295
295
296
this .config = config ;
296
- // We can't just use the existing error reporter, because we might be parsing code that is
297
- // closure-unaware, and there is a conceptually-circular dependency that makes suppressing
298
- // spurious errors difficult: we need to be able to parse a file to determine if the JSDoc was
299
- // annotated as closure-unaware, but that parsing hasn't finished when the existing error
300
- // reporter is called with the parse errors from that code. Instead, we have to locally track
301
- /// whether the parser has seen the relevant closure-unaware annotation, and locally drop the
302
- // errors, handing any other errors off to the provided error reporter.
303
- this .errorReporter = new ClosureUnawareCodeSkippingJsDocInfoErroReporter (this , errorReporter );
297
+ this .errorReporter = errorReporter ;
304
298
this .transformDispatcher = new TransformDispatcher ();
305
299
306
300
if (config .strictMode ().isStrict ()) {
@@ -317,6 +311,7 @@ private static final class CommentTracker {
317
311
private final ImmutableList <Comment > source ;
318
312
private final Predicate <Comment > filter ;
319
313
private int index = -1 ;
314
+ private int previousIndex = -1 ;
320
315
321
316
CommentTracker (ImmutableList <Comment > source , Predicate <Comment > filter ) {
322
317
this .source = source ;
@@ -330,6 +325,7 @@ private static final class CommentTracker {
330
325
}
331
326
332
327
void advance () {
328
+ this .previousIndex = this .index ;
333
329
while (true ) {
334
330
this .index ++; // Always advance at least one element.
335
331
@@ -340,6 +336,10 @@ void advance() {
340
336
}
341
337
}
342
338
339
+ void backtrack () {
340
+ this .index = this .previousIndex ;
341
+ }
342
+
343
343
boolean hasPendingCommentBefore (SourcePosition pos ) {
344
344
Comment c = this .current ();
345
345
// The line number matters for trailing comments
@@ -379,7 +379,10 @@ public static IRFactory transformTree(
379
379
for (Comment comment : tree .sourceComments ) {
380
380
if ((comment .type == Comment .Type .JSDOC || comment .type == Comment .Type .IMPORTANT )
381
381
&& !irFactory .parsedComments .contains (comment )) {
382
- irFactory .handlePossibleFileOverviewJsDoc (comment );
382
+ boolean useLicensesOnlyConfig =
383
+ irFactory .withinClosureUnawareCodeRange (
384
+ comment .location .start .line , comment .location .start .column );
385
+ irFactory .handlePossibleFileOverviewJsDoc (comment , useLicensesOnlyConfig );
383
386
}
384
387
}
385
388
@@ -701,8 +704,8 @@ private boolean handlePossibleFileOverviewJsDoc(JsDocInfoParser jsDocParser) {
701
704
return true ;
702
705
}
703
706
704
- private void handlePossibleFileOverviewJsDoc (Comment comment ) {
705
- JsDocInfoParser jsDocParser = createJsDocInfoParser (comment );
707
+ private void handlePossibleFileOverviewJsDoc (Comment comment , boolean useLicensesOnlyConfig ) {
708
+ JsDocInfoParser jsDocParser = createJsDocInfoParser (comment , useLicensesOnlyConfig );
706
709
parsedComments .add (comment );
707
710
handlePossibleFileOverviewJsDoc (jsDocParser );
708
711
}
@@ -721,6 +724,17 @@ private Comment getJSDocCommentAt(SourcePosition pos) {
721
724
if (comment == null ) {
722
725
return null ;
723
726
}
727
+
728
+ if (withinClosureUnawareCodeRange (comment .location .start .line , comment .location .start .column )) {
729
+ // Within closure-unaware code, we don't parse jsdoc as if it is load-bearing.
730
+ // This early-return prevents the comments here from being recorded as being parsed,
731
+ // and this later allows all these jsdoc comments to be treated as "top-level" comments that
732
+ // might contain license info / "important" comments (the only form of JSDoc that should
733
+ // happen for closure-unaware code).
734
+ this .jsdocTracker .backtrack ();
735
+ return null ;
736
+ }
737
+
724
738
JsDocInfoParser jsDocParser = createJsDocInfoParser (comment );
725
739
parsedComments .add (comment );
726
740
if (handlePossibleFileOverviewJsDoc (jsDocParser )) {
@@ -760,38 +774,78 @@ private Comment getJSDocCommentAt(SourcePosition pos) {
760
774
return parseJSDocInfoFrom (getJSDocCommentAt (tree .getStart ()));
761
775
}
762
776
763
- JSDocInfo parseJSDocInfoOnToken (com .google .javascript .jscomp .parsing .parser .Token token ) {
777
+ @ Nullable JSDocInfo parseJSDocInfoOnToken (
778
+ com .google .javascript .jscomp .parsing .parser .Token token ) {
764
779
return parseJSDocInfoFrom (getJSDocCommentAt (token .getStart ()));
765
780
}
766
781
767
- JSDocInfo parseInlineJSDocAt (SourcePosition pos ) {
782
+ @ Nullable JSDocInfo parseInlineJSDocAt (SourcePosition pos ) {
783
+ if (withinClosureUnawareCodeRange (pos .line , pos .column )) {
784
+ // Within closure-unaware code, we don't parse jsdoc as if it is load-bearing.
785
+ // This early-return prevents the comments here from being recorded as being parsed,
786
+ // and this later allows all these jsdoc comments to be treated as "top-level" comments that
787
+ // might contain license info / "important" comments (the only form of JSDoc that should
788
+ // happen for closure-unaware code).
789
+ // Unlike other points in IRFactory where we explicitly backtrack the jsdocTracker, we don't
790
+ // do that here because we've actually never advanced it yet - that happens in
791
+ // getJSDocCommentAt.
792
+ return null ;
793
+ }
768
794
Comment comment = getJSDocCommentAt (pos );
769
795
return (comment != null && !comment .value .contains ("@" ))
770
796
? parseInlineTypeDoc (comment )
771
797
: parseJSDocInfoFrom (comment );
772
798
}
773
799
800
+ private static final Comparator <Comment > COMMENT_START_POSITION_COMPARATOR =
801
+ comparingInt ((Comment x ) -> x .location .start .line )
802
+ .thenComparingInt (x -> x .location .start .column );
803
+
804
+ private boolean hasAnyPendingCommentBefore (
805
+ SourcePosition pos , boolean withinClosureUnawareCodeRange ) {
806
+ return this .nonJsdocTracker .hasPendingCommentBefore (pos )
807
+ || (withinClosureUnawareCodeRange && this .jsdocTracker .hasPendingCommentBefore (pos ));
808
+ }
809
+
774
810
/**
775
811
* Creates a single NonJSDocComment from every comment associated with this node; or null if there
776
812
* are no such comments.
777
813
*
778
814
* <p>It would be legal to replace all comments associated with this node with that one string.
779
815
*/
780
816
private @ Nullable NonJSDocComment parseNonJSDocCommentAt (SourcePosition pos , boolean isInline ) {
781
- if (config .jsDocParsingMode () != JsDocParsing .INCLUDE_ALL_COMMENTS ) {
782
- return null ;
783
- }
784
-
785
- if (!this .nonJsdocTracker .hasPendingCommentBefore (pos )) {
786
- return null ;
787
- }
817
+ boolean withinClosureUnawareCodeRange = withinClosureUnawareCodeRange (pos .line , pos .column );
788
818
789
819
StringBuilder result = new StringBuilder ();
790
- Comment firstComment = this . nonJsdocTracker . current () ;
820
+ Comment firstComment = null ;
791
821
Comment lastComment = null ;
792
822
793
- while (this . nonJsdocTracker . hasPendingCommentBefore (pos )) {
823
+ while (hasAnyPendingCommentBefore (pos , withinClosureUnawareCodeRange )) {
794
824
Comment currentComment = this .nonJsdocTracker .current ();
825
+ boolean commentFromJsdocTracker = false ;
826
+
827
+ if (withinClosureUnawareCodeRange ) {
828
+ Comment currentJsDocComment = this .jsdocTracker .current ();
829
+ if (currentComment == null
830
+ || (currentJsDocComment != null
831
+ && COMMENT_START_POSITION_COMPARATOR .compare (currentComment , currentJsDocComment )
832
+ > 0 )) {
833
+
834
+ if (currentJsDocComment .value .contains ("@license" )) {
835
+ // Skip this so it can be parsed for the license contents (which happens when a comment
836
+ // isn't inserted into the parsedComments set).
837
+ this .jsdocTracker .advance ();
838
+ continue ;
839
+ }
840
+
841
+ commentFromJsdocTracker = true ;
842
+ currentComment = currentJsDocComment ;
843
+ }
844
+ }
845
+
846
+ if (firstComment == null ) {
847
+ firstComment = currentComment ;
848
+ }
795
849
796
850
if (lastComment != null ) {
797
851
for (int blankCount = currentComment .location .start .line - lastComment .location .end .line ;
@@ -802,8 +856,22 @@ JSDocInfo parseInlineJSDocAt(SourcePosition pos) {
802
856
}
803
857
result .append (currentComment .value );
804
858
859
+
805
860
lastComment = currentComment ;
806
- this .nonJsdocTracker .advance ();
861
+ if (commentFromJsdocTracker ) {
862
+ this .parsedComments .add (currentComment );
863
+ this .jsdocTracker .advance ();
864
+ } else {
865
+ this .nonJsdocTracker .advance ();
866
+ }
867
+ }
868
+
869
+ if (firstComment == null ) {
870
+ return null ; // We didn't find any relevant comments.
871
+ }
872
+
873
+ if (config .jsDocParsingMode () != JsDocParsing .INCLUDE_ALL_COMMENTS ) {
874
+ return null ;
807
875
}
808
876
809
877
NonJSDocComment nonJSDocComment =
@@ -929,10 +997,6 @@ private boolean withinClosureUnawareCodeRange(int line, int lineColumnNo) {
929
997
}
930
998
931
999
private Node maybeInjectCastNode (ParseTree node , JSDocInfo info , Node irNode ) {
932
- if (withinClosureUnawareCodeRange (node .location .start .line , node .location .start .column )) {
933
- // closure-unaware code should never have CAST nodes in the AST.
934
- return irNode ;
935
- }
936
1000
if (node .type == ParseTreeType .PAREN_EXPRESSION && info .hasType ()) {
937
1001
irNode = newNode (Token .CAST , irNode );
938
1002
}
@@ -1006,6 +1070,9 @@ static String languageFeatureWarningMessage(Feature feature) {
1006
1070
1007
1071
void maybeWarnForFeature (ParseTree node , Feature feature ) {
1008
1072
features = features .with (feature );
1073
+ if (withinClosureUnawareCodeRange (node .location .start .line , node .location .start .column )) {
1074
+ return ;
1075
+ }
1009
1076
if (!isSupportedForInputLanguageMode (feature )) {
1010
1077
errorReporter .warning (
1011
1078
languageFeatureWarningMessage (feature ), sourceName , lineno (node ), charno (node ));
@@ -1015,6 +1082,9 @@ void maybeWarnForFeature(ParseTree node, Feature feature) {
1015
1082
void maybeWarnForFeature (
1016
1083
com .google .javascript .jscomp .parsing .parser .Token token , Feature feature ) {
1017
1084
features = features .with (feature );
1085
+ if (withinClosureUnawareCodeRange (token .location .start .line , token .location .start .column )) {
1086
+ return ;
1087
+ }
1018
1088
if (!isSupportedForInputLanguageMode (feature )) {
1019
1089
errorReporter .warning (
1020
1090
languageFeatureWarningMessage (feature ), sourceName , lineno (token ), charno (token ));
@@ -1023,6 +1093,9 @@ void maybeWarnForFeature(
1023
1093
1024
1094
void maybeWarnForFeature (Node node , Feature feature ) {
1025
1095
features = features .with (feature );
1096
+ if (withinClosureUnawareCodeRange (node .getLineno (), node .getCharno ())) {
1097
+ return ;
1098
+ }
1026
1099
if (!isSupportedForInputLanguageMode (feature )) {
1027
1100
errorReporter .warning (
1028
1101
languageFeatureWarningMessage (feature ), sourceName , node .getLineno (), node .getCharno ());
@@ -1054,6 +1127,10 @@ void setSourceInfo(Node node, SourcePosition start, SourcePosition end) {
1054
1127
}
1055
1128
}
1056
1129
1130
+ private JsDocInfoParser createJsDocInfoParser (Comment node ) {
1131
+ return createJsDocInfoParser (node , false );
1132
+ }
1133
+
1057
1134
/**
1058
1135
* Creates a JsDocInfoParser and parses the JsDoc string.
1059
1136
*
@@ -1064,12 +1141,17 @@ void setSourceInfo(Node node, SourcePosition start, SourcePosition end) {
1064
1141
* @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or normal JsDoc, or no JsDoc
1065
1142
* (if the method parses to the wrong level).
1066
1143
*/
1067
- private JsDocInfoParser createJsDocInfoParser (Comment node ) {
1144
+ private JsDocInfoParser createJsDocInfoParser (Comment node , boolean useLicensesOnlyConfig ) {
1068
1145
String comment = node .value ;
1069
1146
int lineno = lineno (node .location .start );
1070
1147
int charno = charno (node .location .start );
1071
1148
int position = node .location .start .offset ;
1072
1149
1150
+ Config config = this .config ;
1151
+ if (useLicensesOnlyConfig ) {
1152
+ config = config .toBuilder ().setJsDocParsingMode (JsDocParsing .LICENSE_COMMENTS_ONLY ).build ();
1153
+ }
1154
+
1073
1155
// The JsDocInfoParser expects the comment without the initial '/**'.
1074
1156
int numOpeningChars = 3 ;
1075
1157
JsDocInfoParser jsdocParser =
@@ -1122,36 +1204,6 @@ void setLengthFrom(Node node, Node ref) {
1122
1204
node .setLength (ref .getLength ());
1123
1205
}
1124
1206
1125
- private static final class ClosureUnawareCodeSkippingJsDocInfoErroReporter
1126
- implements ErrorReporter {
1127
- private final ErrorReporter delegate ;
1128
- private final IRFactory host ;
1129
-
1130
- private ClosureUnawareCodeSkippingJsDocInfoErroReporter (
1131
- IRFactory host , ErrorReporter delegate ) {
1132
- this .delegate = delegate ;
1133
- this .host = host ;
1134
- }
1135
-
1136
- @ Override
1137
- public void error (String message , String sourceName , int line , int lineOffset ) {
1138
- // Line numbers are 1-indexed, but the SourcePosition uses 0-indexed.
1139
- if (host .withinClosureUnawareCodeRange (line - 1 , lineOffset )) {
1140
- return ;
1141
- }
1142
- delegate .error (message , sourceName , line , lineOffset );
1143
- }
1144
-
1145
- @ Override
1146
- public void warning (String message , String sourceName , int line , int lineOffset ) {
1147
- // Line numbers are 1-indexed, but the SourcePosition uses 0-indexed.
1148
- if (host .withinClosureUnawareCodeRange (line - 1 , lineOffset )) {
1149
- return ;
1150
- }
1151
- delegate .warning (message , sourceName , line , lineOffset );
1152
- }
1153
- }
1154
-
1155
1207
private static final QualifiedName GOOG_MODULE = QualifiedName .of ("goog.module" );
1156
1208
1157
1209
private class TransformDispatcher {
0 commit comments