Skip to content

Commit

Permalink
Add support for .i.js file generator to do JSDoc propagation of simpl…
Browse files Browse the repository at this point in the history
…e names.

This allows for typing of useful patterns like:
    class Foo {
      constructor(/** Foo */ foo) {
        /** @const */ this.foo = foo;
      }
    }
without running type-checking.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127451551
  • Loading branch information
blickly committed Jul 14, 2016
1 parent 0d1594f commit f98022b
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 11 deletions.
41 changes: 32 additions & 9 deletions src/com/google/javascript/jscomp/ConvertToTypedInterface.java
Expand Up @@ -75,22 +75,22 @@ public void visit(NodeTraversal t, Node n, Node parent) {
case EXPR_RESULT: case EXPR_RESULT:
if (NodeUtil.isExprAssign(n)) { if (NodeUtil.isExprAssign(n)) {
Node expr = n.getFirstChild(); Node expr = n.getFirstChild();
processName(expr.getFirstChild()); processName(t, expr.getFirstChild());
} }
break; break;
case VAR: case VAR:
case CONST: case CONST:
case LET: case LET:
if (n.getChildCount() == 1) { if (n.getChildCount() == 1) {
processName(n.getFirstChild()); processName(t, n.getFirstChild());
} }
break; break;
default: default:
break; break;
} }
} }


private void processName(Node nameNode) { private void processName(NodeTraversal t, Node nameNode) {
Node jsdocNode = NodeUtil.getBestJSDocInfoNode(nameNode); Node jsdocNode = NodeUtil.getBestJSDocInfoNode(nameNode);
JSDocInfo jsdoc = jsdocNode.getJSDocInfo(); JSDocInfo jsdoc = jsdocNode.getJSDocInfo();
if (!isInferrableConst(jsdoc, nameNode)) { if (!isInferrableConst(jsdoc, nameNode)) {
Expand All @@ -100,14 +100,14 @@ private void processName(Node nameNode) {
if (rhs == null) { if (rhs == null) {
return; return;
} }
JSDocInfo newJsdoc = getTypedJSDoc(rhs, jsdoc); JSDocInfo newJsdoc = getJSDocForRhs(t, rhs, jsdoc);
if (newJsdoc != null) { if (newJsdoc != null) {
jsdocNode.setJSDocInfo(newJsdoc); jsdocNode.setJSDocInfo(newJsdoc);
compiler.reportCodeChange(); compiler.reportCodeChange();
} }
} }


private static JSDocInfo getTypedJSDoc(Node rhs, JSDocInfo oldJSDoc) { private static JSDocInfo getJSDocForRhs(NodeTraversal t, Node rhs, JSDocInfo oldJSDoc) {
switch (NodeUtil.getKnownValueType(rhs)) { switch (NodeUtil.getKnownValueType(rhs)) {
case BOOLEAN: case BOOLEAN:
return getTypeJSDoc(oldJSDoc, "boolean"); return getTypeJSDoc(oldJSDoc, "boolean");
Expand All @@ -119,10 +119,29 @@ private static JSDocInfo getTypedJSDoc(Node rhs, JSDocInfo oldJSDoc) {
return getTypeJSDoc(oldJSDoc, "null"); return getTypeJSDoc(oldJSDoc, "null");
case VOID: case VOID:
return getTypeJSDoc(oldJSDoc, "void"); return getTypeJSDoc(oldJSDoc, "void");
default: case OBJECT:
return null; break;
case UNDETERMINED:
if (rhs.isName()) {
Var decl = t.getScope().getVar(rhs.getString());
return getJSDocForName(decl, oldJSDoc);
}
break;
} }
return null;
} }

private static JSDocInfo getJSDocForName(Var decl, JSDocInfo oldJSDoc) {
if (decl == null) {
return null;
}
JSTypeExpression expr = NodeUtil.getDeclaredTypeExpression(decl.getNameNode());
if (expr == null) {
return null;
}
return getTypeJSDoc(oldJSDoc, expr);
}

} }


private static class RemoveCode implements Callback { private static class RemoveCode implements Callback {
Expand Down Expand Up @@ -427,9 +446,13 @@ private static JSDocInfo maybeUpdateJSDocInfoWithType(JSDocInfo oldJSDoc, Node n
return getTypeJSDoc(oldJSDoc, type.toNonNullAnnotationString()); return getTypeJSDoc(oldJSDoc, type.toNonNullAnnotationString());
} }


private static JSDocInfo getTypeJSDoc(JSDocInfo oldJSDoc, String contents) { private static JSDocInfo getTypeJSDoc(JSDocInfo oldJSDoc, JSTypeExpression newType) {
JSDocInfoBuilder builder = JSDocInfoBuilder.copyFrom(oldJSDoc); JSDocInfoBuilder builder = JSDocInfoBuilder.copyFrom(oldJSDoc);
builder.recordType(new JSTypeExpression(Node.newString(contents), "")); builder.recordType(newType);
return builder.build(); return builder.build();
} }

private static JSDocInfo getTypeJSDoc(JSDocInfo oldJSDoc, String contents) {
return getTypeJSDoc(oldJSDoc, new JSTypeExpression(Node.newString(contents), ""));
}
} }
19 changes: 19 additions & 0 deletions src/com/google/javascript/jscomp/NodeUtil.java
Expand Up @@ -26,6 +26,7 @@
import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder; import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticSourceFile; import com.google.javascript.rhino.StaticSourceFile;
import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Token;
Expand Down Expand Up @@ -4080,6 +4081,24 @@ private static boolean isToStringMethodCall(Node call) {
return false; return false;
} }


/** Return declared JSDoc type for the given name declaration, or null if none present. */
@Nullable
static JSTypeExpression getDeclaredTypeExpression(Node declaration) {
Preconditions.checkArgument(declaration.isName());
JSDocInfo nameJsdoc = getBestJSDocInfo(declaration);
if (nameJsdoc != null) {
return nameJsdoc.getType();
}
Node parent = declaration.getParent();
if (parent.isParamList()) {
JSDocInfo functionJsdoc = getBestJSDocInfo(parent.getParent());
if (functionJsdoc != null) {
return functionJsdoc.getParameterType(declaration.getString());
}
}
return null;
}

/** Find the best JSDoc for the given node. */ /** Find the best JSDoc for the given node. */
@Nullable @Nullable
public static JSDocInfo getBestJSDocInfo(Node n) { public static JSDocInfo getBestJSDocInfo(Node n) {
Expand Down
57 changes: 55 additions & 2 deletions test/com/google/javascript/jscomp/ConvertToTypedInterfaceTest.java
Expand Up @@ -34,8 +34,7 @@ public void testInferAnnotatedTypeFromTypeInference() {
"/** @constructor */ function Foo() {} \n /** @const {number} */ Foo.prototype.x;"); "/** @constructor */ function Foo() {} \n /** @const {number} */ Foo.prototype.x;");
} }


public void testConstJsdocPropagation() { public void testSimpleConstJsdocPropagation() {

test("/** @const */ var x = 5;", "/** @const {number} */ var x;"); test("/** @const */ var x = 5;", "/** @const {number} */ var x;");
test("/** @const */ var x = true;", "/** @const {boolean} */ var x;"); test("/** @const */ var x = true;", "/** @const {boolean} */ var x;");
test("/** @const */ var x = 'str';", "/** @const {string} */ var x;"); test("/** @const */ var x = 'str';", "/** @const {string} */ var x;");
Expand All @@ -51,6 +50,60 @@ public void testConstJsdocPropagation() {
ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE); ConvertToTypedInterface.CONSTANT_WITHOUT_EXPLICIT_TYPE);
} }


public void testConstJsdocPropagationForNames() {
test(
"/** @type {!Array<string>} */ var x = []; /** @const */ var y = x;",
"/** @type {!Array<string>} */ var x; /** @const {!Array<string>} */ var y;");

test(
LINE_JOINER.join(
"/** @constructor */",
"function Foo(/** number */ x) {",
" /** @const */ this.x = x;",
"}"),
LINE_JOINER.join(
"/** @constructor */ function Foo(/** number */ x) {}",
"/** @const {number} */ Foo.prototype.x;"));

test(
LINE_JOINER.join(
"/** @constructor @param {!Array<string>} arr */",
"function Foo(arr) {",
" /** @const */ this.arr = arr;",
"}"),
LINE_JOINER.join(
"/** @constructor @param {!Array<string>} arr */ function Foo(arr) {}",
"/** @const {!Array<string>} */ Foo.prototype.arr;"));

testEs6(
LINE_JOINER.join(
"class Foo {",
" constructor(/** number */ x) {",
" /** @const */ this.x = x;",
" }",
"}"),
LINE_JOINER.join(
"class Foo {",
" constructor(/** number */ x) {}",
"}",
"/** @const {number} */ Foo.prototype.x;"));

testEs6(
LINE_JOINER.join(
"class Foo {",
" /** @param {number} x */",
" constructor(x) {",
" /** @const */ this.x = x;",
" }",
"}"),
LINE_JOINER.join(
"class Foo {",
" /** @param {number} x */",
" constructor(x) {}",
"}",
"/** @const {number} */ Foo.prototype.x;"));
}

public void testRemoveUselessStatements() { public void testRemoveUselessStatements() {
test("34", ""); test("34", "");
test("'str'", ""); test("'str'", "");
Expand Down

0 comments on commit f98022b

Please sign in to comment.