diff --git a/src/com/google/javascript/jscomp/CompilerInput.java b/src/com/google/javascript/jscomp/CompilerInput.java index 3021d580254..e435bd38b32 100644 --- a/src/com/google/javascript/jscomp/CompilerInput.java +++ b/src/com/google/javascript/jscomp/CompilerInput.java @@ -190,6 +190,16 @@ public ImmutableList getProvides() { return getDependencyInfo().getProvides(); } + @Override + public boolean getHasExternsAnnotation() { + return getDependencyInfo().getHasExternsAnnotation(); + } + + @Override + public boolean getHasNoCompileAnnotation() { + return getDependencyInfo().getHasNoCompileAnnotation(); + } + /** * Gets a list of types provided, but does not attempt to * regenerate the dependency information. Typically this occurs @@ -271,6 +281,8 @@ private DependencyInfo getDependencyInfo() { .setRequires(concat(dependencyInfo.getRequires(), extraRequires)) .setTypeRequires(dependencyInfo.getTypeRequires()) .setLoadFlags(dependencyInfo.getLoadFlags()) + .setHasExternsAnnotation(dependencyInfo.getHasExternsAnnotation()) + .setHasNoCompileAnnotation(dependencyInfo.getHasNoCompileAnnotation()) .build(); extraRequires.clear(); extraProvides.clear(); diff --git a/src/com/google/javascript/jscomp/JSModule.java b/src/com/google/javascript/jscomp/JSModule.java index 8af6d16e056..02696fa1e4d 100644 --- a/src/com/google/javascript/jscomp/JSModule.java +++ b/src/com/google/javascript/jscomp/JSModule.java @@ -96,6 +96,16 @@ public ImmutableList getProvides() { return ImmutableList.of(name); } + @Override + public boolean getHasExternsAnnotation() { + return false; + } + + @Override + public boolean getHasNoCompileAnnotation() { + return false; + } + @Override public ImmutableList getRequires() { ImmutableList.Builder builder = ImmutableList.builder(); diff --git a/src/com/google/javascript/jscomp/LazyParsedDependencyInfo.java b/src/com/google/javascript/jscomp/LazyParsedDependencyInfo.java index f608f85bf6d..9197909c98e 100644 --- a/src/com/google/javascript/jscomp/LazyParsedDependencyInfo.java +++ b/src/com/google/javascript/jscomp/LazyParsedDependencyInfo.java @@ -91,4 +91,14 @@ public ImmutableList getTypeRequires() { public ImmutableList getProvides() { return delegate.getProvides(); } + + @Override + public boolean getHasExternsAnnotation() { + return delegate.getHasExternsAnnotation(); + } + + @Override + public boolean getHasNoCompileAnnotation() { + return delegate.getHasNoCompileAnnotation(); + } } diff --git a/src/com/google/javascript/jscomp/deps/DependencyInfo.java b/src/com/google/javascript/jscomp/deps/DependencyInfo.java index 85a09f24af7..dcc0c14e5c4 100644 --- a/src/com/google/javascript/jscomp/deps/DependencyInfo.java +++ b/src/com/google/javascript/jscomp/deps/DependencyInfo.java @@ -145,6 +145,12 @@ abstract static class Builder { /** Whether the symbol is provided by a module */ boolean isModule(); + /** Whether the file '@externs' annotation. */ + boolean getHasExternsAnnotation(); + + /** Whether the file has the '@nocompile' annotation. */ + boolean getHasNoCompileAnnotation(); + /** * Abstract base implementation that defines derived accessors such * as {@link #isModule}. diff --git a/src/com/google/javascript/jscomp/deps/JsFileLineParser.java b/src/com/google/javascript/jscomp/deps/JsFileLineParser.java index 434a064ea23..3ac3e717a8d 100644 --- a/src/com/google/javascript/jscomp/deps/JsFileLineParser.java +++ b/src/com/google/javascript/jscomp/deps/JsFileLineParser.java @@ -130,21 +130,27 @@ void doParse(String filePath, Reader fileContents) { String line = null; lineNum = 0; boolean inMultilineComment = false; + boolean inJsDocComment = false; try { while (null != (line = lineBuffer.readLine())) { ++lineNum; try { String revisedLine = line; - String revisedBlockCommentLine = ""; + String revisedJsDocCommentLine = ""; if (inMultilineComment) { int endOfComment = revisedLine.indexOf("*/"); if (endOfComment != -1) { - revisedBlockCommentLine = revisedLine.substring(0, endOfComment + 2); + if (inJsDocComment) { + revisedJsDocCommentLine = revisedLine.substring(0, endOfComment + 2); + inJsDocComment = false; + } revisedLine = revisedLine.substring(endOfComment + 2); inMultilineComment = false; } else { - revisedBlockCommentLine = line; + if (inJsDocComment) { + revisedJsDocCommentLine = line; + } revisedLine = ""; } } @@ -166,17 +172,25 @@ void doParse(String filePath, Reader fileContents) { if (isCommentQuoted(revisedLine, startOfMultilineComment, '"')) { break; } + if (startOfMultilineComment == revisedLine.indexOf("/**")) { + inJsDocComment = true; + } int endOfMultilineComment = revisedLine.indexOf("*/", startOfMultilineComment + 2); if (endOfMultilineComment == -1) { - revisedBlockCommentLine = revisedLine.substring(startOfMultilineComment); + if (inJsDocComment) { + revisedJsDocCommentLine = revisedLine.substring(startOfMultilineComment); + } revisedLine = revisedLine.substring(0, startOfMultilineComment); inMultilineComment = true; break; } else { - if (!parseBlockCommentLine( - revisedLine.substring(startOfMultilineComment, endOfMultilineComment + 2)) - && shortcutMode) { - break; + if (inJsDocComment) { + String jsDocComment = + revisedLine.substring(startOfMultilineComment, endOfMultilineComment + 2); + if (!parseJsDocCommentLine(jsDocComment) && shortcutMode) { + break; + } + inJsDocComment = false; } revisedLine = revisedLine.substring(0, startOfMultilineComment) + @@ -188,8 +202,8 @@ void doParse(String filePath, Reader fileContents) { } } - if (!revisedBlockCommentLine.isEmpty()) { - if (!parseBlockCommentLine(revisedBlockCommentLine) && shortcutMode) { + if (!revisedJsDocCommentLine.isEmpty()) { + if (!parseJsDocCommentLine(revisedJsDocCommentLine) && shortcutMode) { break; } } @@ -252,7 +266,13 @@ private boolean isCommentQuoted(String line, int startOfMultilineComment, char q */ abstract boolean parseLine(String line) throws ParseException; - boolean parseBlockCommentLine(String line) { + /** + * Called for each JSDoc line of the file being parsed. + * + * @param line The JSDoc comment line to parse. + * @return true to keep going, false otherwise. + */ + boolean parseJsDocCommentLine(String line) { return true; } diff --git a/src/com/google/javascript/jscomp/deps/JsFileParser.java b/src/com/google/javascript/jscomp/deps/JsFileParser.java index 6d94fde117b..b00004a4c6c 100644 --- a/src/com/google/javascript/jscomp/deps/JsFileParser.java +++ b/src/com/google/javascript/jscomp/deps/JsFileParser.java @@ -90,6 +90,12 @@ public final class JsFileParser extends JsFileLineParser { /** Line in comment indicating that the file is Closure's base.js. */ private static final String PROVIDES_GOOG_COMMENT = "@provideGoog"; + /** Line in comment indicating that the file is an extern. */ + private static final String EXTERNS_COMMENT = "@externs"; + + /** Line in comment indicating that the file should not be touched by JSCompiler. */ + private static final String NOCOMPILE_COMMENT = "@nocompile"; + /** The start of a bundled goog.module, i.e. one that is wrapped in a goog.loadModule call */ private static final String BUNDLED_GOOG_MODULE_START = "goog.loadModule(function("; @@ -104,6 +110,8 @@ public final class JsFileParser extends JsFileLineParser { private List requires; private List typeRequires; private boolean fileHasProvidesOrRequires; + private boolean hasExternsAnnotation; + private boolean hasNoCompileAnnotation; private ModuleLoader loader = ModuleLoader.EMPTY; private ModuleLoader.ModulePath file; @@ -211,6 +219,8 @@ private DependencyInfo parseReader(String filePath, .setRequires(requires) .setTypeRequires(typeRequires) .setLoadFlags(loadFlags) + .setHasExternsAnnotation(hasExternsAnnotation) + .setHasNoCompileAnnotation(hasNoCompileAnnotation) .build(); if (logger.isLoggable(Level.FINE)) { logger.fine("DepInfo: " + dependencyInfo); @@ -245,10 +255,20 @@ private void setModuleType(ModuleType type) { } @Override - protected boolean parseBlockCommentLine(String line) { + protected boolean parseJsDocCommentLine(String line) { + // Since these checks are all mutually exclusive, bail out after we see the first one. + // @providesGoog should only ever be in one file (namely base.js), + // and @nocompile means the file is thrown out entirely, + // so it should never exist with @externs. if (includeGoogBase && line.contains(PROVIDES_GOOG_COMMENT)) { provides.add("goog"); return false; + } else if (line.contains(EXTERNS_COMMENT)) { + hasExternsAnnotation = true; + return false; + } else if (line.contains(NOCOMPILE_COMMENT)) { + hasNoCompileAnnotation = true; + return false; } return true; } diff --git a/src/com/google/javascript/jscomp/deps/SimpleDependencyInfo.java b/src/com/google/javascript/jscomp/deps/SimpleDependencyInfo.java index ac4aa6adf5c..c63d077fc1a 100644 --- a/src/com/google/javascript/jscomp/deps/SimpleDependencyInfo.java +++ b/src/com/google/javascript/jscomp/deps/SimpleDependencyInfo.java @@ -36,7 +36,9 @@ public static Builder builder(String srcPathRelativeToClosure, String pathOfDefi .setProvides(ImmutableList.of()) .setRequires(ImmutableList.of()) .setTypeRequires(ImmutableList.of()) - .setLoadFlags(ImmutableMap.of()); + .setLoadFlags(ImmutableMap.of()) + .setHasExternsAnnotation(false) + .setHasNoCompileAnnotation(false); } /** @@ -52,7 +54,9 @@ public static Builder from(DependencyInfo copy) { .setProvides(copy.getProvides()) .setRequires(copy.getRequires()) .setTypeRequires(copy.getTypeRequires()) - .setLoadFlags(copy.getLoadFlags()); + .setLoadFlags(copy.getLoadFlags()) + .setHasExternsAnnotation(copy.getHasExternsAnnotation()) + .setHasNoCompileAnnotation(copy.getHasNoCompileAnnotation()); } abstract Builder setName(String name); @@ -71,6 +75,10 @@ public static Builder from(DependencyInfo copy) { public abstract Builder setLoadFlags(Map loadFlags); + public abstract Builder setHasExternsAnnotation(boolean hasExternsAnnotation); + + public abstract Builder setHasNoCompileAnnotation(boolean hasNoCompileAnnotation); + private static final ImmutableMap GOOG_MODULE_FLAGS = ImmutableMap.of("module", "goog"); diff --git a/test/com/google/javascript/jscomp/deps/JsFileLineParserTest.java b/test/com/google/javascript/jscomp/deps/JsFileLineParserTest.java index c86f8485138..989c5a6aaf8 100644 --- a/test/com/google/javascript/jscomp/deps/JsFileLineParserTest.java +++ b/test/com/google/javascript/jscomp/deps/JsFileLineParserTest.java @@ -159,7 +159,7 @@ boolean parseLine(String line) { } @Override - boolean parseBlockCommentLine(String line) { + boolean parseJsDocCommentLine(String line) { comments.append(line); return true; } diff --git a/test/com/google/javascript/jscomp/deps/JsFileParserTest.java b/test/com/google/javascript/jscomp/deps/JsFileParserTest.java index a1787322d99..fa5dfb2543c 100644 --- a/test/com/google/javascript/jscomp/deps/JsFileParserTest.java +++ b/test/com/google/javascript/jscomp/deps/JsFileParserTest.java @@ -611,6 +611,89 @@ public void testIncludeGoog4() { assertDeps(expected, result); } + @Test + public void testExternsAnnotation_basic_multiline() { + String contents = "/**\n" + " * @externs\n" + " */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH).setHasExternsAnnotation(true).build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + + @Test + public void testExternsAnnotation_basic_oneLine() { + String contents = "/** @externs */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH).setHasExternsAnnotation(true).build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + + @Test + public void testExternsAnnotation_blockComment() { + String contents = "/* @externs */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH).setHasExternsAnnotation(false).build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + + @Test + public void testExternsAnnotation_blockComment_multiline() { + String contents = "/*\n @externs */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH).setHasExternsAnnotation(false).build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + + @Test + public void testNoCompileAnnotation_basic_multiline() { + String contents = "/**\n" + " * @nocompile\n" + " */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH) + .setHasNoCompileAnnotation(true) + .build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + + @Test + public void testNoCompileAnnotation_basic_oneLine() { + String contents = "/** @nocompile */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH) + .setHasNoCompileAnnotation(true) + .build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + + @Test + public void testNoCompileAnnotation_blockComment() { + String contents = "/* @nocompile */\n"; + + DependencyInfo expected = + SimpleDependencyInfo.builder(CLOSURE_PATH, SRC_PATH) + .setHasNoCompileAnnotation(false) + .build(); + DependencyInfo result = + parser.setIncludeGoogBase(true).parseFile(SRC_PATH, CLOSURE_PATH, contents); + assertDeps(expected, result); + } + @Test public void testParseProvidesAndWrappedGoogModule() { String contents =