diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index dfe6569e3c1..dcd21a9a3fe 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2407,8 +2407,8 @@ namespace ts { function bindPropertyAssignment(functionName: __String, propertyAccess: PropertyAccessExpression, isPrototypeProperty: boolean) { const symbol = lookupSymbolForName(functionName); - let targetSymbol = symbol && isDeclarationOfJavascriptExpression(symbol) ? - (symbol.valueDeclaration as VariableDeclaration).initializer.symbol : + let targetSymbol = symbol && isDeclarationOfJavascriptContainerExpression(symbol) ? (symbol.valueDeclaration as VariableDeclaration).initializer.symbol : + symbol && isDeclarationOfDefaultedJavascriptContainerExpression(symbol) ? ((symbol.valueDeclaration as VariableDeclaration).initializer as BinaryExpression).right.symbol : symbol; Debug.assert(propertyAccess.parent.kind === SyntaxKind.BinaryExpression || propertyAccess.parent.kind === SyntaxKind.ExpressionStatement); let isLegalPosition: boolean; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 483d2f174bb..8491a9d9c52 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4359,6 +4359,10 @@ namespace ts { if (isInJavaScriptFile(declaration) && isJSDocPropertyLikeTag(declaration) && declaration.typeExpression) { return links.type = getTypeFromTypeNode(declaration.typeExpression.type); } + if (isInJavaScriptFile(declaration) && isDeclarationOfDefaultedJavascriptContainerExpression(symbol)) { + // !!! (probably out of place, probably not the right function to call) + return links.type = checkExpression(((declaration as VariableDeclaration).initializer as BinaryExpression).right); + } // Handle variable, parameter or property if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return unknownType; @@ -4379,7 +4383,7 @@ namespace ts { || isIdentifier(declaration) || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) || isMethodSignature(declaration)) { - + // TODO: Might need to add a case here? // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { return getTypeOfFuncClassEnumModule(symbol); @@ -14744,15 +14748,18 @@ namespace ts { let spread: Type = emptyObjectType; let propagatedFlags: TypeFlags = TypeFlags.FreshLiteral; - const contextualType = getApparentTypeOfContextualType(node); + const isInJSFile = isInJavaScriptFile(node); + // TODO: Might need to skip if isDeclarationOfDefaultedJavascriptContainerExpression(node.parent.parent) instead of just checking node.properties.length > 0 + // (actually, it would be better not to skip contextual typing at all, but to do that I need to avoid the checking loop another way) + const contextualType = node.properties.length > 0 && getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); - const isJSObjectLiteral = !contextualType && isInJavaScriptFile(node); + const isJSObjectLiteral = !contextualType && isInJSFile; let typeFlags: TypeFlags = 0; let patternWithComputedProperties = false; let hasComputedStringProperty = false; let hasComputedNumberProperty = false; - const isInJSFile = isInJavaScriptFile(node); + // TODO: This seems like the wrong way to put the assignment-properties into the type (especially the manual call to mergeSymbolTable) if (isInJSFile && node.symbol && node.symbol.exports) { mergeSymbolTable(propertiesTable, node.symbol.exports); node.symbol.exports.forEach(symbol => propertiesArray.push(symbol)); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index eefce516508..a25e372a0aa 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1489,14 +1489,29 @@ namespace ts { * Returns true if the node is a variable declaration whose initializer is a function or class expression, or an empty object literal. * This function does not test if the node is in a JavaScript file or not. */ - export function isDeclarationOfJavascriptExpression(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 || - (declaration.initializer.kind === SyntaxKind.ObjectLiteralExpression && - (declaration.initializer as ObjectLiteralExpression).properties.length === 0)); + export function isDeclarationOfJavascriptContainerExpression(s: Symbol) { + if (s.valueDeclaration && isVariableDeclaration(s.valueDeclaration)) { + return isVariableDeclaration(s.valueDeclaration) && isJavascriptContainerExpression(s.valueDeclaration.initializer); + } + return false; + } + + export function isJavascriptContainerExpression(expression: Expression | undefined) { + return expression && + (expression.kind === SyntaxKind.FunctionExpression || + expression.kind === SyntaxKind.ClassExpression || + isObjectLiteralExpression(expression) && expression.properties.length === 0) + } + + export function isDeclarationOfDefaultedJavascriptContainerExpression(s: Symbol) { + if (s.valueDeclaration && isVariableDeclaration(s.valueDeclaration)) { + return s.valueDeclaration.initializer && + isBinaryExpression(s.valueDeclaration.initializer) && + isIdentifier(s.valueDeclaration.initializer.left) && + isIdentifier(s.valueDeclaration.name) && + s.valueDeclaration.initializer.left.escapedText === s.valueDeclaration.name.escapedText && + isObjectLiteralExpression(s.valueDeclaration.initializer.right) && + s.valueDeclaration.initializer.right.properties.length === 0; } return false; }