Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More special declaration types in JS #21974

Merged
merged 52 commits into from
Mar 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
8bd66a0
JS Object literal assignments are declarations
sandersn Nov 29, 2017
c6a7751
Test:js object literal assignment as declaration
sandersn Nov 29, 2017
4f07f58
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Feb 7, 2018
9e8d59c
Support `var x = x || {}` declarations in JS
sandersn Feb 7, 2018
a51bce0
Test:basic `var x = x || {}` support in JS
sandersn Feb 7, 2018
2f8c237
Support `o.x = o.x || {}` assignments in JS
sandersn Feb 7, 2018
7e3fdc2
Test:`o.x = o.x || {}` assignments in JS
sandersn Feb 7, 2018
e0596ad
Improve contextual type skip in checkObjectLiteral
sandersn Feb 7, 2018
d3b02be
Update baselines
sandersn Feb 7, 2018
4998b99
getSpecialPropertyAssignmentKind uses type guards
sandersn Feb 8, 2018
d0b08a2
Refactor JS static property assignment binding
sandersn Feb 8, 2018
b0aebb4
Recursive object-literal-assignment declarations
sandersn Feb 8, 2018
a09c239
4-nested object-literal assignment works in JS
sandersn Feb 9, 2018
8ac94f5
Support function/class in JS nested decls
sandersn Feb 9, 2018
33c084f
Return baselines to original state
sandersn Feb 9, 2018
61ea026
Allow window. prefix in default-assignment JS decl
sandersn Feb 9, 2018
15554d7
Fix bogus jsdoc error
sandersn Feb 10, 2018
03d155f
Update tests and baselines
sandersn Feb 10, 2018
fc08e20
Correctly merge JS decls
sandersn Feb 13, 2018
88c67fa
Refactor binder and update baselines.
sandersn Feb 13, 2018
76a9ac4
Restrict declaration initializers too
sandersn Feb 13, 2018
bad155f
Clean up bindPropertyAssignment
sandersn Feb 14, 2018
0cadfcf
Clean up js decl code in checker+utilities
sandersn Feb 14, 2018
4fdef85
Naming and duplication cleanup
sandersn Feb 15, 2018
d2b933e
Cleanup in binder: rename and move
sandersn Feb 15, 2018
0191b70
Further cleanup
sandersn Feb 15, 2018
8f98c77
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Feb 15, 2018
8bfcf33
Add symbols for just-added test
sandersn Feb 15, 2018
d180117
Move skipParentheses to utilities
sandersn Feb 15, 2018
54a89ac
Simplify bindPropertyAssignment inner loop
sandersn Feb 15, 2018
518f651
Remove assert hit by chrome devtools+update baselines
sandersn Feb 20, 2018
ad43240
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Feb 20, 2018
5af91a9
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Feb 20, 2018
116a8a8
Support nested prototype declarations
sandersn Feb 20, 2018
8e424f9
Cleanup new code in binder
sandersn Feb 20, 2018
01f2ee3
Set up structure of prototype assignments
sandersn Feb 21, 2018
b14cf4e
First draft of prototype assignment
sandersn Feb 22, 2018
41fba6f
Incremental prototype+prototype assignment work
sandersn Feb 22, 2018
d55aa22
Code cleanup
sandersn Feb 22, 2018
aa88f71
Fix js-prototype-assignment on declarations
sandersn Feb 22, 2018
dd25236
Fix nested js-containers+proto assignment in types space
sandersn Feb 23, 2018
5d32a31
Merge branch 'js-prototype-assignment' into js-object-literal-assignm…
sandersn Feb 23, 2018
aa6b76f
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Feb 23, 2018
4099d48
Update chrome-devtools baseline
sandersn Feb 23, 2018
c3143d2
Support js nested namespace decls on exports
sandersn Feb 27, 2018
c318089
Remove assert for undeclared js-nested-exports
sandersn Feb 27, 2018
125a317
Fix lint
sandersn Feb 27, 2018
d86440f
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Mar 7, 2018
239f214
Address PR comments
sandersn Mar 8, 2018
04ceb3d
Disallow JS/non-JS merge without crashing
sandersn Mar 8, 2018
35730f2
Improve error span:duplicate symbols cross-js/ts
sandersn Mar 8, 2018
f8134d0
Merge branch 'master' into js-object-literal-assignments-as-declarations
sandersn Mar 8, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 115 additions & 70 deletions src/compiler/binder.ts

Large diffs are not rendered by default.

144 changes: 100 additions & 44 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4266,16 +4266,6 @@ namespace ts {
return node;
}

export function skipParentheses(node: Expression): Expression;
export function skipParentheses(node: Node): Node;
export function skipParentheses(node: Node): Node {
while (node.kind === SyntaxKind.ParenthesizedExpression) {
node = (<ParenthesizedExpression>node).expression;
}

return node;
}

export function skipAssertions(node: Expression): Expression;
export function skipAssertions(node: Node): Node;
export function skipAssertions(node: Node): Node {
Expand Down
8 changes: 5 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3260,8 +3260,8 @@ namespace ts {

Enum = RegularEnum | ConstEnum,
Variable = FunctionScopedVariable | BlockScopedVariable,
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor,
Type = Class | Interface | Enum | EnumMember | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias,
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | JSContainer,
Type = Class | Interface | Enum | EnumMember | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias | JSContainer,
Namespace = ValueModule | NamespaceModule | Enum,
Module = ValueModule | NamespaceModule,
Accessor = GetAccessor | SetAccessor,
Expand Down Expand Up @@ -4000,7 +4000,9 @@ namespace ts {
/// this.name = expr
ThisProperty,
// F.name = expr
Property
Property,
// F.prototype = { ... }
Prototype,
}

export interface JsFileExtensionInfo {
Expand Down
200 changes: 160 additions & 40 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1472,13 +1472,115 @@ namespace ts {
}

/**
* Returns true if the node is a variable declaration whose initializer is a function or class expression.
* This function does not test if the node is in a JavaScript file or not.
* Given the symbol of a declaration, find the symbol of its Javascript container-like initializer,
* if it has one. Otherwise just return the original symbol.
*
* Container-like initializer behave like namespaces, so the binder needs to add contained symbols
* to their exports. An example is a function with assignments to `this` inside.
*/
export function getJSInitializerSymbol(symbol: Symbol) {
if (!symbol || !symbol.valueDeclaration) {
return symbol;
}
const declaration = symbol.valueDeclaration;
const e = getDeclaredJavascriptInitializer(declaration) || getAssignedJavascriptInitializer(declaration);
return e && e.symbol ? e.symbol : symbol;
}

/** Get the declaration initializer, when the initializer is container-like (See getJavascriptInitializer) */
export function getDeclaredJavascriptInitializer(node: Node) {
if (node && isVariableDeclaration(node) && node.initializer) {
return getJavascriptInitializer(node.initializer, /*isPrototypeAssignment*/ false) ||
isIdentifier(node.name) && getDefaultedJavascriptInitializer(node.name, node.initializer, /*isPrototypeAssignment*/ false);
}
}

/**
* Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getJavascriptInitializer).
* We treat the right hand side of assignments with container-like initalizers as declarations.
*/
export function getAssignedJavascriptInitializer(node: Node) {
if (node && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
const isPrototypeAssignment = isPropertyAccessExpression(node.parent.left) && node.parent.left.name.escapedText === "prototype";
return getJavascriptInitializer(node.parent.right, isPrototypeAssignment) ||
getDefaultedJavascriptInitializer(node.parent.left as EntityNameExpression, node.parent.right, isPrototypeAssignment);
}
}

/**
* Recognized Javascript container-like initializers are:
* 1. (function() {})() -- IIFEs
* 2. function() { } -- Function expressions
* 3. class { } -- Class expressions
* 4. {} -- Empty object literals
* 5. { ... } -- Non-empty object literals, when used to initialize a prototype, like `C.prototype = { m() { } }`
*
* This function returns the provided initializer, or undefined if it is not valid.
*/
export function getJavascriptInitializer(initializer: Node, isPrototypeAssignment: boolean): Expression {
if (isCallExpression(initializer)) {
const e = skipParentheses(initializer.expression);
return e.kind === SyntaxKind.FunctionExpression || e.kind === SyntaxKind.ArrowFunction ? initializer : undefined;
}
if (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ClassExpression) {
return initializer as Expression;
}
if (isObjectLiteralExpression(initializer) && (initializer.properties.length === 0 || isPrototypeAssignment)) {
return initializer;
}
}

/**
* A defaulted Javascript initializer matches the pattern
* `Lhs = Lhs || JavascriptInitializer`
* or `var Lhs = Lhs || JavascriptInitializer`
*
* The second Lhs is required to be the same as the first except that it may be prefixed with
* 'window.', 'global.' or 'self.' The second Lhs is otherwise ignored by the binder and checker.
*/
function getDefaultedJavascriptInitializer(name: EntityNameExpression, initializer: Expression, isPrototypeAssignment: boolean) {
const e = isBinaryExpression(initializer) && initializer.operatorToken.kind === SyntaxKind.BarBarToken && getJavascriptInitializer(initializer.right, isPrototypeAssignment);
if (e && isSameEntityName(name, (initializer as BinaryExpression).left as EntityNameExpression)) {
return e;
}
}

/** Given a Javascript initializer, return the outer name. That is, the lhs of the assignment or the declaration name. */
export function getOuterNameOfJsInitializer(node: Declaration): DeclarationName | undefined {
if (isBinaryExpression(node.parent)) {
const parent = (node.parent.operatorToken.kind === SyntaxKind.BarBarToken && isBinaryExpression(node.parent.parent)) ? node.parent.parent : node.parent;
if (parent.operatorToken.kind === SyntaxKind.EqualsToken && isIdentifier(parent.left)) {
return parent.left;
}
}
else if (isVariableDeclaration(node.parent)) {
return node.parent.name;
}
}

/**
* Is the 'declared' name the same as the one in the initializer?
* @return true for identical entity names, as well as ones where the initializer is prefixed with
* 'window', 'self' or 'global'. For example:
*
* var my = my || {}
* var min = window.min || {}
* my.app = self.my.app || class { }
*/
export function isDeclarationOfFunctionOrClassExpression(s: Symbol) {
if (s.valueDeclaration && s.valueDeclaration.kind === SyntaxKind.VariableDeclaration) {
const declaration = s.valueDeclaration as VariableDeclaration;
return declaration.initializer && (declaration.initializer.kind === SyntaxKind.FunctionExpression || declaration.initializer.kind === SyntaxKind.ClassExpression);
function isSameEntityName(name: EntityNameExpression, initializer: EntityNameExpression): boolean {
if (isIdentifier(name) && isIdentifier(initializer)) {
return name.escapedText === initializer.escapedText;
}
if (isIdentifier(name) && isPropertyAccessExpression(initializer)) {
return (initializer.expression.kind as SyntaxKind.ThisKeyword === SyntaxKind.ThisKeyword ||
isIdentifier(initializer.expression) &&
(initializer.expression.escapedText === "window" as __String ||
initializer.expression.escapedText === "self" as __String ||
initializer.expression.escapedText === "global" as __String)) &&
isSameEntityName(name, initializer.name);
}
if (isPropertyAccessExpression(name) && isPropertyAccessExpression(initializer)) {
return name.name.escapedText === initializer.name.escapedText && isSameEntityName(name.expression, initializer.expression);
}
return false;
}
Expand All @@ -1501,47 +1603,44 @@ namespace ts {
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
/// assignments we treat as special in the binder
export function getSpecialPropertyAssignmentKind(expr: BinaryExpression): SpecialPropertyAssignmentKind {
if (!isInJavaScriptFile(expr)) {
if (!isInJavaScriptFile(expr) ||
expr.operatorToken.kind !== SyntaxKind.EqualsToken ||
!isPropertyAccessExpression(expr.left)) {
return SpecialPropertyAssignmentKind.None;
}
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || expr.left.kind !== SyntaxKind.PropertyAccessExpression) {
return SpecialPropertyAssignmentKind.None;
const lhs = expr.left;
if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return SpecialPropertyAssignmentKind.ThisProperty;
}
const lhs = <PropertyAccessExpression>expr.left;
if (lhs.expression.kind === SyntaxKind.Identifier) {
const lhsId = <Identifier>lhs.expression;
if (lhsId.escapedText === "exports") {
// exports.name = expr
return SpecialPropertyAssignmentKind.ExportsProperty;
else if (isIdentifier(lhs.expression) && lhs.expression.escapedText === "module" && lhs.name.escapedText === "exports") {
// module.exports = expr
return SpecialPropertyAssignmentKind.ModuleExports;
}
else if (isEntityNameExpression(lhs.expression)) {
if (lhs.name.escapedText === "prototype" && isObjectLiteralExpression(expr.right)) {
// F.prototype = { ... }
return SpecialPropertyAssignmentKind.Prototype;
}
else if (lhsId.escapedText === "module" && lhs.name.escapedText === "exports") {
// module.exports = expr
return SpecialPropertyAssignmentKind.ModuleExports;
else if (isPropertyAccessExpression(lhs.expression) && lhs.expression.name.escapedText === "prototype") {
// F.G....prototype.x = expr
return SpecialPropertyAssignmentKind.PrototypeProperty;
}
else {
// F.x = expr
return SpecialPropertyAssignmentKind.Property;

let nextToLast = lhs;
while (isPropertyAccessExpression(nextToLast.expression)) {
nextToLast = nextToLast.expression;
}
}
else if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return SpecialPropertyAssignmentKind.ThisProperty;
}
else if (lhs.expression.kind === SyntaxKind.PropertyAccessExpression) {
// chained dot, e.g. x.y.z = expr; this var is the 'x.y' part
const innerPropertyAccess = <PropertyAccessExpression>lhs.expression;
if (innerPropertyAccess.expression.kind === SyntaxKind.Identifier) {
// module.exports.name = expr
const innerPropertyAccessIdentifier = <Identifier>innerPropertyAccess.expression;
if (innerPropertyAccessIdentifier.escapedText === "module" && innerPropertyAccess.name.escapedText === "exports") {
return SpecialPropertyAssignmentKind.ExportsProperty;
}
if (innerPropertyAccess.name.escapedText === "prototype") {
return SpecialPropertyAssignmentKind.PrototypeProperty;
}
Debug.assert(isIdentifier(nextToLast.expression));
const id = nextToLast.expression as Identifier;
if (id.escapedText === "exports" ||
id.escapedText === "module" && nextToLast.name.escapedText === "exports") {
// exports.name = expr OR module.exports.name = expr
return SpecialPropertyAssignmentKind.ExportsProperty;
}
// F.G...x = expr
return SpecialPropertyAssignmentKind.Property;
}


return SpecialPropertyAssignmentKind.None;
}

Expand Down Expand Up @@ -1617,6 +1716,15 @@ namespace ts {
node.expression.right;
}

function getSourceOfDefaultedAssignment(node: Node): Node {
return isExpressionStatement(node) &&
isBinaryExpression(node.expression) &&
getSpecialPropertyAssignmentKind(node.expression) !== SpecialPropertyAssignmentKind.None &&
isBinaryExpression(node.expression.right) &&
node.expression.right.operatorToken.kind === SyntaxKind.BarBarToken &&
node.expression.right.right;
}

function getSingleInitializerOfVariableStatementOrPropertyDeclaration(node: Node): Expression | undefined {
switch (node.kind) {
case SyntaxKind.VariableStatement:
Expand Down Expand Up @@ -1660,7 +1768,8 @@ namespace ts {
(getSingleVariableOfVariableStatement(parent.parent) === node || getSourceOfAssignment(parent.parent))) {
getJSDocCommentsAndTagsWorker(parent.parent);
}
if (parent && parent.parent && parent.parent.parent && getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node) {
if (parent && parent.parent && parent.parent.parent &&
(getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.parent.parent) === node || getSourceOfDefaultedAssignment(parent.parent.parent))) {
getJSDocCommentsAndTagsWorker(parent.parent.parent);
}
if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None ||
Expand Down Expand Up @@ -1702,7 +1811,8 @@ namespace ts {

export function getHostSignatureFromJSDoc(node: JSDocParameterTag): FunctionLike | undefined {
const host = getJSDocHost(node);
const decl = getSourceOfAssignment(host) ||
const decl = getSourceOfDefaultedAssignment(host) ||
getSourceOfAssignment(host) ||
getSingleInitializerOfVariableStatementOrPropertyDeclaration(host) ||
getSingleVariableOfVariableStatement(host) ||
getNestedModuleDeclaration(host) ||
Expand Down Expand Up @@ -1843,6 +1953,16 @@ namespace ts {
return walkUp(node, SyntaxKind.ParenthesizedExpression);
}

export function skipParentheses(node: Expression): Expression;
export function skipParentheses(node: Node): Node;
export function skipParentheses(node: Node): Node {
while (node.kind === SyntaxKind.ParenthesizedExpression) {
node = (<ParenthesizedExpression>node).expression;
}

return node;
}

// a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped
export function isDeleteTarget(node: Node): boolean {
if (node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) {
Expand Down
2 changes: 1 addition & 1 deletion src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ namespace FourSlash {
const actual = actualCompletions.entries;

if (actual.length !== expected.length) {
this.raiseError(`Expected ${expected.length} completions, got ${actual.map(a => a.name)}.`);
this.raiseError(`Expected ${expected.length} completions, got ${actual.length} (${actual.map(a => a.name)}).`);
}

ts.zipWith(actual, expected, (completion, expectedCompletion, index) => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/convertFunctionToEs6Class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,4 @@ namespace ts.codefix {
function getModifierKindFromSource(source: Node, kind: SyntaxKind): ReadonlyArray<Modifier> {
return filter(source.modifiers, modifier => modifier.kind === kind);
}
}
}
1 change: 1 addition & 0 deletions src/services/navigationBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ namespace ts.NavigationBar {
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.ModuleExports:
case SpecialPropertyAssignmentKind.PrototypeProperty:
case SpecialPropertyAssignmentKind.Prototype:
addNodeWithRecursiveChild(node, (node as BinaryExpression).right);
break;
case SpecialPropertyAssignmentKind.ThisProperty:
Expand Down
2 changes: 2 additions & 0 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ namespace ts {
case SpecialPropertyAssignmentKind.Property:
// static method / property
return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement;
case SpecialPropertyAssignmentKind.Prototype:
return ScriptElementKind.localClassElement;
default: {
assertTypeIsNever(kind);
return ScriptElementKind.unknown;
Expand Down
34 changes: 17 additions & 17 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1960,28 +1960,28 @@ declare namespace ts {
JSContainer = 67108864,
Enum = 384,
Variable = 3,
Value = 107455,
Type = 793064,
Value = 67216319,
Type = 67901928,
Namespace = 1920,
Module = 1536,
Accessor = 98304,
FunctionScopedVariableExcludes = 107454,
BlockScopedVariableExcludes = 107455,
ParameterExcludes = 107455,
FunctionScopedVariableExcludes = 67216318,
BlockScopedVariableExcludes = 67216319,
ParameterExcludes = 67216319,
PropertyExcludes = 0,
EnumMemberExcludes = 900095,
FunctionExcludes = 106927,
ClassExcludes = 899519,
InterfaceExcludes = 792968,
RegularEnumExcludes = 899327,
ConstEnumExcludes = 899967,
ValueModuleExcludes = 106639,
EnumMemberExcludes = 68008959,
FunctionExcludes = 67215791,
ClassExcludes = 68008383,
InterfaceExcludes = 67901832,
RegularEnumExcludes = 68008191,
ConstEnumExcludes = 68008831,
ValueModuleExcludes = 67215503,
NamespaceModuleExcludes = 0,
MethodExcludes = 99263,
GetAccessorExcludes = 41919,
SetAccessorExcludes = 74687,
TypeParameterExcludes = 530920,
TypeAliasExcludes = 793064,
MethodExcludes = 67208127,
GetAccessorExcludes = 67150783,
SetAccessorExcludes = 67183551,
TypeParameterExcludes = 67639784,
TypeAliasExcludes = 67901928,
AliasExcludes = 2097152,
ModuleMember = 2623475,
ExportHasLocal = 944,
Expand Down
Loading