-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Property assignments in Typescript #26368
Changes from 5 commits
bfcf324
3309104
b0fe52f
3060706
872fbbf
f090caf
0b953b3
869dc69
05c2ffc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4788,48 +4788,45 @@ namespace ts { | |
} | ||
|
||
/** If we don't have an explicit JSDoc type, get the type from the initializer. */ | ||
function getInitializerTypeFromSpecialDeclarations(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: Expression, special: SpecialPropertyAssignmentKind) { | ||
if (isBinaryExpression(expression)) { | ||
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right)); | ||
if (type.flags & TypeFlags.Object && | ||
special === SpecialPropertyAssignmentKind.ModuleExports && | ||
symbol.escapedName === InternalSymbolName.ExportEquals) { | ||
const exportedType = resolveStructuredTypeMembers(type as ObjectType); | ||
const members = createSymbolTable(); | ||
copyEntries(exportedType.members, members); | ||
if (resolvedSymbol && !resolvedSymbol.exports) { | ||
resolvedSymbol.exports = createSymbolTable(); | ||
} | ||
(resolvedSymbol || symbol).exports!.forEach((s, name) => { | ||
if (members.has(name)) { | ||
const exportedMember = exportedType.members.get(name)!; | ||
const union = createSymbol(s.flags | exportedMember.flags, name); | ||
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); | ||
members.set(name, union); | ||
} | ||
else { | ||
members.set(name, s); | ||
} | ||
}); | ||
const result = createAnonymousType( | ||
exportedType.symbol, | ||
members, | ||
exportedType.callSignatures, | ||
exportedType.constructSignatures, | ||
exportedType.stringIndexInfo, | ||
exportedType.numberIndexInfo); | ||
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag | ||
return result; | ||
function getInitializerTypeFromSpecialDeclarations(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression, special: SpecialPropertyAssignmentKind) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. getInitializerTypeFromSpecialDeclarations is only called once, so I just made the caller check for undefined. You should turn on Ignore Whitespace to read this review. |
||
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right)); | ||
if (type.flags & TypeFlags.Object && | ||
special === SpecialPropertyAssignmentKind.ModuleExports && | ||
symbol.escapedName === InternalSymbolName.ExportEquals) { | ||
const exportedType = resolveStructuredTypeMembers(type as ObjectType); | ||
const members = createSymbolTable(); | ||
copyEntries(exportedType.members, members); | ||
if (resolvedSymbol && !resolvedSymbol.exports) { | ||
resolvedSymbol.exports = createSymbolTable(); | ||
} | ||
if (isEmptyArrayLiteralType(type)) { | ||
if (noImplicitAny) { | ||
reportImplicitAnyError(expression, anyArrayType); | ||
(resolvedSymbol || symbol).exports!.forEach((s, name) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you briefly convince me that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inside the containing |
||
if (members.has(name)) { | ||
const exportedMember = exportedType.members.get(name)!; | ||
const union = createSymbol(s.flags | exportedMember.flags, name); | ||
union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); | ||
members.set(name, union); | ||
} | ||
return anyArrayType; | ||
else { | ||
members.set(name, s); | ||
} | ||
}); | ||
const result = createAnonymousType( | ||
exportedType.symbol, | ||
members, | ||
exportedType.callSignatures, | ||
exportedType.constructSignatures, | ||
exportedType.stringIndexInfo, | ||
exportedType.numberIndexInfo); | ||
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag | ||
return result; | ||
} | ||
if (isEmptyArrayLiteralType(type)) { | ||
if (noImplicitAny) { | ||
reportImplicitAnyError(expression, anyArrayType); | ||
} | ||
return type; | ||
return anyArrayType; | ||
} | ||
return neverType; | ||
return type; | ||
} | ||
|
||
function isDeclarationInConstructor(expression: Expression) { | ||
|
@@ -5049,7 +5046,9 @@ namespace ts { | |
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { | ||
return getTypeOfFuncClassEnumModule(symbol); | ||
} | ||
type = tryGetTypeFromEffectiveTypeNode(declaration) || anyType; | ||
type = isBinaryExpression(declaration.parent) ? | ||
getWidenedTypeFromJSSpecialPropertyDeclarations(symbol) : | ||
tryGetTypeFromEffectiveTypeNode(declaration) || anyType; | ||
} | ||
else if (isPropertyAssignment(declaration)) { | ||
type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); | ||
|
@@ -16004,7 +16003,24 @@ namespace ts { | |
case SpecialPropertyAssignmentKind.PrototypeProperty: | ||
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. | ||
// See `bindStaticPropertyAssignment` in `binder.ts`. | ||
return !binaryExpression.left.symbol || binaryExpression.left.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.left.symbol.valueDeclaration); | ||
if (!binaryExpression.left.symbol) { | ||
return true; | ||
} | ||
else { | ||
const decl = binaryExpression.left.symbol.valueDeclaration; | ||
if (!decl) { | ||
return false; | ||
} | ||
if (isInJavaScriptFile(decl)) { | ||
return !!getJSDocTypeTag(decl); | ||
} | ||
else if (isIdentifier((binaryExpression.left as PropertyAccessExpression).expression)) { | ||
const id = (binaryExpression.left as PropertyAccessExpression).expression as Identifier; | ||
const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); | ||
return !isTSFunctionSymbol(parentSymbol); | ||
} | ||
return true; | ||
} | ||
case SpecialPropertyAssignmentKind.ThisProperty: | ||
case SpecialPropertyAssignmentKind.ModuleExports: | ||
return !binaryExpression.symbol || binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1719,12 +1719,15 @@ namespace ts { | |
} | ||
|
||
export function getDeclarationOfJSInitializer(node: Node): Node | undefined { | ||
if (!isInJavaScriptFile(node) || !node.parent) { | ||
if (!node.parent) { | ||
return undefined; | ||
} | ||
let name: Expression | BindingName | undefined; | ||
let decl: Node | undefined; | ||
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { | ||
if (!isInJavaScriptFile(node) && !isVarConst(node.parent)) { | ||
return undefined; | ||
} | ||
name = node.parent.name; | ||
decl = node.parent; | ||
} | ||
|
@@ -1886,8 +1889,12 @@ 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) || | ||
expr.operatorToken.kind !== SyntaxKind.EqualsToken || | ||
const special = getSpecialPropertyAssignmentKindWorker(expr); | ||
return special === SpecialPropertyAssignmentKind.Property || isInJavaScriptFile(expr) ? special : SpecialPropertyAssignmentKind.None; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't want to create another wrapper/worker pair, but I couldn't come up with a better way to special-case the isJS check just for property assignments. Any ideas? |
||
} | ||
|
||
function getSpecialPropertyAssignmentKindWorker(expr: BinaryExpression): SpecialPropertyAssignmentKind { | ||
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || | ||
!isPropertyAccessExpression(expr.left)) { | ||
return SpecialPropertyAssignmentKind.None; | ||
} | ||
|
@@ -1948,6 +1955,15 @@ namespace ts { | |
!!getJSDocTypeTag(expr.parent); | ||
} | ||
|
||
export function isTSFunctionSymbol(symbol: Symbol | undefined) { | ||
if (!symbol || !symbol.valueDeclaration) { | ||
return false; | ||
} | ||
const decl = symbol.valueDeclaration; | ||
return !isInJavaScriptFile(decl) && | ||
(decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer)); | ||
} | ||
|
||
export function importFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport { | ||
return tryGetImportFromModuleSpecifier(node) || Debug.fail(Debug.showSyntaxKind(node.parent)); | ||
} | ||
|
@@ -2348,7 +2364,10 @@ namespace ts { | |
} | ||
else { | ||
const binExp = name.parent.parent; | ||
return isBinaryExpression(binExp) && getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None && getNameOfDeclaration(binExp) === name; | ||
return isBinaryExpression(binExp) && | ||
getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None && | ||
(isInJavaScriptFile(name) || !isPropertyAccessExpression(binExp.left) || binExp.left.expression.symbol) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests seem to pass without this line -- what's this protecting against? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FindAllReferences relies on this function, and perhaps other services do. Without this line, a few fourslash tests fail, pointing out that getSpecialPropertyAssignmentKind now recognizes any property assignment in TS files, even those that aren’t actually expando assignments. The left side of the or restores the old behavior for JS, and the right side adds to it for TS expando assignments. However, the right side isn’t tested. I need to add a case similar to the existing JS ones. |
||
getNameOfDeclaration(binExp) === name; | ||
} | ||
} | ||
default: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: In both places
isTsFunctionSymbol
is called, you just checkedisInJavaScriptFile
to be false. So maybe it could just beisFunctionSymbol
.