Skip to content

Commit

Permalink
Add a new gwt entry point to help slice up JS Doc for ES5 -> ES6 clas…
Browse files Browse the repository at this point in the history
…s conversions.

Parameters need to move to the constructor and everything else stay on
the class (e.g. @implements, @template, description, etc).

The compiler is the only thing that understands how to parse JS Doc
correctly and it is very hard to reimplement this logic. I think this is
the best of some not fantastic options.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=242717398
  • Loading branch information
johnplaisted authored and brad4d committed Apr 10, 2019
1 parent 65b4e4f commit 1ffabc3
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 36 deletions.
@@ -0,0 +1,124 @@
/*
* Copyright 2019 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.gwt.client;

import com.google.common.base.Strings;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.regexp.shared.RegExp;
import com.google.javascript.jscomp.JSDocInfoPrinter;
import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.jscomp.parsing.Config.JsDocParsing;
import com.google.javascript.jscomp.parsing.Config.LanguageMode;
import com.google.javascript.jscomp.parsing.JsDocInfoParser;
import com.google.javascript.jscomp.parsing.JsDocTokenStream;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import jsinterop.annotations.JsMethod;

/**
* Utilities to parse JS doc and assist in upgrade ES5 to ES6 classes where JS Doc must be split
* into class and constructor comments.
*/
final class Es6ClassConverterJsDocHelper implements EntryPoint {

private Es6ClassConverterJsDocHelper() {}

@Override
public void onModuleLoad() {
exportCode();
}

private native void exportCode() /*-{
var getClassJsDoc = $entry(@com.google.javascript.jscomp.gwt.client.Es6ClassConverterJsDocHelper::getClassJsDoc(*));
var getConstructorJsDoc = $entry(@com.google.javascript.jscomp.gwt.client.Es6ClassConverterJsDocHelper::getConstructorJsDoc(*));
if (typeof module !== 'undefined' && module.exports) {
module.exports.getClassJsDoc = getClassJsDoc;
module.exports.getConstructorJsDoc = getConstructorJsDoc;
}
}-*/;

/**
* Gets JS Doc that should be retained on a class. Used to upgrade ES5 to ES6 classes and separate
* class from constructor comments.
*/
@JsMethod(name = "getClassJsDoc", namespace = "jscomp")
public static String getClassJsDoc(String jsDoc) {
if (Strings.isNullOrEmpty(jsDoc)) {
return null;
}
Config config =
Config.builder()
.setLanguageMode(LanguageMode.ECMASCRIPT3)
.setStrictMode(Config.StrictMode.SLOPPY)
.setJsDocParsingMode(JsDocParsing.INCLUDE_DESCRIPTIONS_WITH_WHITESPACE)
.build();
JsDocInfoParser parser =
new JsDocInfoParser(
// Stream expects us to remove the leading /**
new JsDocTokenStream(jsDoc.substring(3)),
jsDoc,
0,
null,
config,
ErrorReporter.NULL_INSTANCE);
parser.parse();
JSDocInfo parsed = parser.retrieveAndResetParsedJSDocInfo();
JSDocInfo classComments = parsed.cloneClassDoc();
JSDocInfoPrinter printer =
new JSDocInfoPrinter(/* useOriginalName= */ true, /* printDesc= */ true);
String comment = printer.print(classComments);
// Don't return empty comments, return null instead.
if (comment == null || RegExp.compile("\\s*/\\*\\*\\s*\\*/\\s*").test(comment)) {
return null;
}
return comment.trim();
}

/**
* Gets JS Doc that should be moved to a constructor. Used to upgrade ES5 to ES6 classes and
* separate class from constructor comments.
*/
@JsMethod(name = "getConstructorJsDoc", namespace = "jscomp")
public static String getConstructorJsDoc(String jsDoc) {
if (Strings.isNullOrEmpty(jsDoc)) {
return null;
}
Config config =
Config.builder()
.setLanguageMode(LanguageMode.ECMASCRIPT3)
.setStrictMode(Config.StrictMode.SLOPPY)
.setJsDocParsingMode(JsDocParsing.INCLUDE_DESCRIPTIONS_WITH_WHITESPACE)
.build();
JsDocInfoParser parser =
new JsDocInfoParser(
// Stream expects us to remove the leading /**
new JsDocTokenStream(jsDoc.substring(3)),
jsDoc,
0,
null,
config,
ErrorReporter.NULL_INSTANCE);
parser.parse();
JSDocInfo parsed = parser.retrieveAndResetParsedJSDocInfo();
JSDocInfo params = parsed.cloneConstructorDoc();
if (parsed.getParameterNames().isEmpty() && parsed.getSuppressions().isEmpty()) {
return null;
}
JSDocInfoPrinter printer =
new JSDocInfoPrinter(/* useOriginalName= */ true, /* printDesc= */ true);
return printer.print(params).trim();
}
}
35 changes: 18 additions & 17 deletions src/com/google/javascript/jscomp/parsing/Config.java
Expand Up @@ -119,34 +119,34 @@ public enum RunMode {
}

/** Language level to accept. */
abstract LanguageMode languageMode();
public abstract LanguageMode languageMode();

/** Whether to assume input is strict mode compliant. */
abstract StrictMode strictMode();
public abstract StrictMode strictMode();

/** How to parse the descriptions of JsDoc comments. */
abstract JsDocParsing jsDocParsingMode();
public abstract JsDocParsing jsDocParsingMode();

/** Whether to keep going after encountering a parse error. */
abstract RunMode runMode();
public abstract RunMode runMode();

/** Recognized JSDoc annotations, mapped from their name to their internal representation. */
abstract ImmutableMap<String, Annotation> annotations();
public abstract ImmutableMap<String, Annotation> annotations();

/** Set of recognized names in a {@code @suppress} tag. */
abstract ImmutableSet<String> suppressionNames();
public abstract ImmutableSet<String> suppressionNames();

/** Set of recognized names in a {@code @closurePrimitive} tag. */
abstract ImmutableSet<String> closurePrimitiveNames();

/** Whether to parse inline source maps (//# sourceMappingURL=data:...). */
abstract boolean parseInlineSourceMaps();
public abstract boolean parseInlineSourceMaps();

final ImmutableSet<String> annotationNames() {
return annotations().keySet();
}

static Builder builder() {
public static Builder builder() {
return new AutoValue_Config.Builder()
.setLanguageMode(LanguageMode.TYPESCRIPT)
.setStrictMode(StrictMode.STRICT)
Expand All @@ -158,30 +158,31 @@ static Builder builder() {
.setParseInlineSourceMaps(false);
}

/** Builder for a Config. */
@AutoValue.Builder
abstract static class Builder {
abstract Builder setLanguageMode(LanguageMode mode);
public abstract static class Builder {
public abstract Builder setLanguageMode(LanguageMode mode);

abstract Builder setStrictMode(StrictMode mode);
public abstract Builder setStrictMode(StrictMode mode);

abstract Builder setJsDocParsingMode(JsDocParsing mode);
public abstract Builder setJsDocParsingMode(JsDocParsing mode);

abstract Builder setRunMode(RunMode mode);
public abstract Builder setRunMode(RunMode mode);

abstract Builder setParseInlineSourceMaps(boolean parseInlineSourceMaps);
public abstract Builder setParseInlineSourceMaps(boolean parseInlineSourceMaps);

abstract Builder setSuppressionNames(Iterable<String> names);
public abstract Builder setSuppressionNames(Iterable<String> names);

abstract Builder setClosurePrimitiveNames(Iterable<String> names);

final Builder setExtraAnnotationNames(Iterable<String> names) {
return setAnnotations(buildAnnotations(names));
}

abstract Config build();
public abstract Config build();

// The following is intended to be used internally only (but isn't private due to AutoValue).
abstract Builder setAnnotations(ImmutableMap<String, Annotation> names);
public abstract Builder setAnnotations(ImmutableMap<String, Annotation> names);
}

/** Create the annotation names from the user-specified annotation whitelist. */
Expand Down
26 changes: 12 additions & 14 deletions src/com/google/javascript/jscomp/parsing/JsDocInfoParser.java
Expand Up @@ -160,13 +160,13 @@ private enum State {
NEXT_IS_ANNOTATION
}


JsDocInfoParser(JsDocTokenStream stream,
String comment,
int commentPosition,
Node templateNode,
Config config,
ErrorReporter errorReporter) {
public JsDocInfoParser(
JsDocTokenStream stream,
String comment,
int commentPosition,
Node templateNode,
Config config,
ErrorReporter errorReporter) {
this.stream = stream;

boolean parseDocumentation = config.jsDocParsingMode().shouldParseDescriptions();
Expand Down Expand Up @@ -261,14 +261,12 @@ private static JsDocInfoParser getParser(String toParse) {
}

/**
* Parses a {@link JSDocInfo} object. This parsing method reads all tokens
* returned by the {@link JsDocTokenStream#getJsDocToken()} method until the
* {@link JsDocToken#EOC} is returned.
* Parses a {@link JSDocInfo} object. This parsing method reads all tokens returned by the {@link
* JsDocTokenStream#getJsDocToken()} method until the {@link JsDocToken#EOC} is returned.
*
* @return {@code true} if JSDoc information was correctly parsed,
* {@code false} otherwise
* @return {@code true} if JSDoc information was correctly parsed, {@code false} otherwise
*/
boolean parse() {
public boolean parse() {
state = State.SEARCHING_ANNOTATION;
skipEOLs();

Expand Down Expand Up @@ -2763,7 +2761,7 @@ private boolean hasParsedFileOverviewDocInfo() {
return jsdocBuilder.isPopulatedWithFileOverview();
}

JSDocInfo retrieveAndResetParsedJSDocInfo() {
public JSDocInfo retrieveAndResetParsedJSDocInfo() {
return jsdocBuilder.build();
}

Expand Down
Expand Up @@ -23,18 +23,18 @@
/**
* This class implements the scanner for JsDoc strings.
*
* It is heavily based on Rhino's TokenStream.
* <p>It is heavily based on Rhino's TokenStream.
*
*/
class JsDocTokenStream {
public class JsDocTokenStream {
/*
* For chars - because we need something out-of-range
* to check. (And checking EOF by exception is annoying.)
* Note distinction from EOF token type!
*/
private static final int EOF_CHAR = -1;

JsDocTokenStream(String sourceString) {
public JsDocTokenStream(String sourceString) {
this(sourceString, 0);
}

Expand Down
81 changes: 79 additions & 2 deletions src/com/google/javascript/rhino/JSDocInfo.java
Expand Up @@ -156,7 +156,7 @@ public String toString() {
.toString();
}

@SuppressWarnings("MissingOverride") // Adding @Override breaks the GWT compilation.
@SuppressWarnings("MissingOverride") // Adding @Override breaks the GWT compilation.
protected LazilyInitializedInfo clone() {
return clone(false);
}
Expand Down Expand Up @@ -188,6 +188,20 @@ protected LazilyInitializedInfo clone(boolean cloneTypeNodes) {
return other;
}

protected LazilyInitializedInfo cloneClassDoc() {
LazilyInitializedInfo other = clone(/* cloneTypeNodes= */ false);
other.parameters = null;
other.suppressions = null;
return other;
}

protected LazilyInitializedInfo cloneConstructorDoc() {
LazilyInitializedInfo other = new LazilyInitializedInfo();
other.parameters = cloneTypeMap(parameters, /* cloneTypeExpressionNodes= */ false);
other.suppressions = suppressions == null ? null : ImmutableSet.copyOf(suppressions);
return other;
}

protected ArrayList<JSTypeExpression> cloneTypeList(
ArrayList<JSTypeExpression> list, boolean cloneTypeExpressionNodes) {
ArrayList<JSTypeExpression> newlist = null;
Expand Down Expand Up @@ -248,6 +262,31 @@ private static final class LazilyInitializedDocumentation implements Serializabl

private List<String> authors;
private List<String> sees;

LazilyInitializedDocumentation cloneConstructorDoc() {
LazilyInitializedDocumentation other = new LazilyInitializedDocumentation();
if (parameters != null) {
other.parameters = new LinkedHashMap<>(parameters);
}
return other;
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("sourceComment", sourceComment)
.add("markers", markers)
.add("parameters", parameters)
.add("throwsDescriptions", throwsDescriptions)
.add("blockDescription", blockDescription)
.add("fileOverview", fileOverview)
.add("returnDescription", returnDescription)
.add("version", version)
.add("authors", authors)
.add("sees", sees)
.omitNullValues()
.toString();
}
}

/**
Expand Down Expand Up @@ -561,6 +600,40 @@ public JSDocInfo clone(boolean cloneTypeNodes) {
return other;
}

/**
* This is used to get all nodes + the description, excluding the param nodes. Used to help in an
* ES5 to ES6 class converter only.
*/
public JSDocInfo cloneClassDoc() {
JSDocInfo other = new JSDocInfo();
other.info = this.info == null ? null : this.info.cloneClassDoc();
other.documentation = this.documentation;
other.visibility = this.visibility;
other.bitset = this.bitset;
other.type = cloneType(this.type, false);
other.thisType = cloneType(this.thisType, false);
other.includeDocumentation = this.includeDocumentation;
other.originalCommentPosition = this.originalCommentPosition;
other.setConstructor(false);
other.setStruct(false);
if (!isInterface() && other.info != null) {
other.info.baseType = null;
}
return other;
}

/**
* This is used to get only the parameter nodes. Used to help in an ES5 to ES6 converter class
* only.
*/
public JSDocInfo cloneConstructorDoc() {
JSDocInfo other = new JSDocInfo();
other.info = this.info == null ? null : this.info.cloneConstructorDoc();
other.documentation =
this.documentation == null ? null : this.documentation.cloneConstructorDoc();
return other;
}

private static JSTypeExpression cloneType(JSTypeExpression expr, boolean cloneTypeNodes) {
if (expr != null) {
return cloneTypeNodes ? expr.copy() : expr;
Expand Down Expand Up @@ -666,7 +739,11 @@ void setUnrestricted() {
}

void setStruct() {
setFlag(true, MASK_STRUCT);
setStruct(true);
}

void setStruct(boolean value) {
setFlag(value, MASK_STRUCT);
}

void setDict() {
Expand Down

0 comments on commit 1ffabc3

Please sign in to comment.