@@ -1472,13 +1472,115 @@ namespace ts {
14721472 }
14731473
14741474 /**
1475- * Returns true if the node is a variable declaration whose initializer is a function or class expression.
1476- * This function does not test if the node is in a JavaScript file or not.
1475+ * Given the symbol of a declaration, find the symbol of its Javascript container-like initializer,
1476+ * if it has one. Otherwise just return the original symbol.
1477+ *
1478+ * Container-like initializer behave like namespaces, so the binder needs to add contained symbols
1479+ * to their exports. An example is a function with assignments to `this` inside.
1480+ */
1481+ export function getJSInitializerSymbol ( symbol : Symbol ) {
1482+ if ( ! symbol || ! symbol . valueDeclaration ) {
1483+ return symbol ;
1484+ }
1485+ const declaration = symbol . valueDeclaration ;
1486+ const e = getDeclaredJavascriptInitializer ( declaration ) || getAssignedJavascriptInitializer ( declaration ) ;
1487+ return e && e . symbol ? e . symbol : symbol ;
1488+ }
1489+
1490+ /** Get the declaration initializer, when the initializer is container-like (See getJavascriptInitializer) */
1491+ export function getDeclaredJavascriptInitializer ( node : Node ) {
1492+ if ( node && isVariableDeclaration ( node ) && node . initializer ) {
1493+ return getJavascriptInitializer ( node . initializer , /*isPrototypeAssignment*/ false ) ||
1494+ isIdentifier ( node . name ) && getDefaultedJavascriptInitializer ( node . name , node . initializer , /*isPrototypeAssignment*/ false ) ;
1495+ }
1496+ }
1497+
1498+ /**
1499+ * Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getJavascriptInitializer).
1500+ * We treat the right hand side of assignments with container-like initalizers as declarations.
1501+ */
1502+ export function getAssignedJavascriptInitializer ( node : Node ) {
1503+ if ( node && node . parent && isBinaryExpression ( node . parent ) && node . parent . operatorToken . kind === SyntaxKind . EqualsToken ) {
1504+ const isPrototypeAssignment = isPropertyAccessExpression ( node . parent . left ) && node . parent . left . name . escapedText === "prototype" ;
1505+ return getJavascriptInitializer ( node . parent . right , isPrototypeAssignment ) ||
1506+ getDefaultedJavascriptInitializer ( node . parent . left as EntityNameExpression , node . parent . right , isPrototypeAssignment ) ;
1507+ }
1508+ }
1509+
1510+ /**
1511+ * Recognized Javascript container-like initializers are:
1512+ * 1. (function() {})() -- IIFEs
1513+ * 2. function() { } -- Function expressions
1514+ * 3. class { } -- Class expressions
1515+ * 4. {} -- Empty object literals
1516+ * 5. { ... } -- Non-empty object literals, when used to initialize a prototype, like `C.prototype = { m() { } }`
1517+ *
1518+ * This function returns the provided initializer, or undefined if it is not valid.
1519+ */
1520+ export function getJavascriptInitializer ( initializer : Node , isPrototypeAssignment : boolean ) : Expression {
1521+ if ( isCallExpression ( initializer ) ) {
1522+ const e = skipParentheses ( initializer . expression ) ;
1523+ return e . kind === SyntaxKind . FunctionExpression || e . kind === SyntaxKind . ArrowFunction ? initializer : undefined ;
1524+ }
1525+ if ( initializer . kind === SyntaxKind . FunctionExpression || initializer . kind === SyntaxKind . ClassExpression ) {
1526+ return initializer as Expression ;
1527+ }
1528+ if ( isObjectLiteralExpression ( initializer ) && ( initializer . properties . length === 0 || isPrototypeAssignment ) ) {
1529+ return initializer ;
1530+ }
1531+ }
1532+
1533+ /**
1534+ * A defaulted Javascript initializer matches the pattern
1535+ * `Lhs = Lhs || JavascriptInitializer`
1536+ * or `var Lhs = Lhs || JavascriptInitializer`
1537+ *
1538+ * The second Lhs is required to be the same as the first except that it may be prefixed with
1539+ * 'window.', 'global.' or 'self.' The second Lhs is otherwise ignored by the binder and checker.
1540+ */
1541+ function getDefaultedJavascriptInitializer ( name : EntityNameExpression , initializer : Expression , isPrototypeAssignment : boolean ) {
1542+ const e = isBinaryExpression ( initializer ) && initializer . operatorToken . kind === SyntaxKind . BarBarToken && getJavascriptInitializer ( initializer . right , isPrototypeAssignment ) ;
1543+ if ( e && isSameEntityName ( name , ( initializer as BinaryExpression ) . left as EntityNameExpression ) ) {
1544+ return e ;
1545+ }
1546+ }
1547+
1548+ /** Given a Javascript initializer, return the outer name. That is, the lhs of the assignment or the declaration name. */
1549+ export function getOuterNameOfJsInitializer ( node : Declaration ) : DeclarationName | undefined {
1550+ if ( isBinaryExpression ( node . parent ) ) {
1551+ const parent = ( node . parent . operatorToken . kind === SyntaxKind . BarBarToken && isBinaryExpression ( node . parent . parent ) ) ? node . parent . parent : node . parent ;
1552+ if ( parent . operatorToken . kind === SyntaxKind . EqualsToken && isIdentifier ( parent . left ) ) {
1553+ return parent . left ;
1554+ }
1555+ }
1556+ else if ( isVariableDeclaration ( node . parent ) ) {
1557+ return node . parent . name ;
1558+ }
1559+ }
1560+
1561+ /**
1562+ * Is the 'declared' name the same as the one in the initializer?
1563+ * @return true for identical entity names, as well as ones where the initializer is prefixed with
1564+ * 'window', 'self' or 'global'. For example:
1565+ *
1566+ * var my = my || {}
1567+ * var min = window.min || {}
1568+ * my.app = self.my.app || class { }
14771569 */
1478- export function isDeclarationOfFunctionOrClassExpression ( s : Symbol ) {
1479- if ( s . valueDeclaration && s . valueDeclaration . kind === SyntaxKind . VariableDeclaration ) {
1480- const declaration = s . valueDeclaration as VariableDeclaration ;
1481- return declaration . initializer && ( declaration . initializer . kind === SyntaxKind . FunctionExpression || declaration . initializer . kind === SyntaxKind . ClassExpression ) ;
1570+ function isSameEntityName ( name : EntityNameExpression , initializer : EntityNameExpression ) : boolean {
1571+ if ( isIdentifier ( name ) && isIdentifier ( initializer ) ) {
1572+ return name . escapedText === initializer . escapedText ;
1573+ }
1574+ if ( isIdentifier ( name ) && isPropertyAccessExpression ( initializer ) ) {
1575+ return ( initializer . expression . kind as SyntaxKind . ThisKeyword === SyntaxKind . ThisKeyword ||
1576+ isIdentifier ( initializer . expression ) &&
1577+ ( initializer . expression . escapedText === "window" as __String ||
1578+ initializer . expression . escapedText === "self" as __String ||
1579+ initializer . expression . escapedText === "global" as __String ) ) &&
1580+ isSameEntityName ( name , initializer . name ) ;
1581+ }
1582+ if ( isPropertyAccessExpression ( name ) && isPropertyAccessExpression ( initializer ) ) {
1583+ return name . name . escapedText === initializer . name . escapedText && isSameEntityName ( name . expression , initializer . expression ) ;
14821584 }
14831585 return false ;
14841586 }
@@ -1501,47 +1603,44 @@ namespace ts {
15011603 /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
15021604 /// assignments we treat as special in the binder
15031605 export function getSpecialPropertyAssignmentKind ( expr : BinaryExpression ) : SpecialPropertyAssignmentKind {
1504- if ( ! isInJavaScriptFile ( expr ) ) {
1606+ if ( ! isInJavaScriptFile ( expr ) ||
1607+ expr . operatorToken . kind !== SyntaxKind . EqualsToken ||
1608+ ! isPropertyAccessExpression ( expr . left ) ) {
15051609 return SpecialPropertyAssignmentKind . None ;
15061610 }
1507- if ( expr . operatorToken . kind !== SyntaxKind . EqualsToken || expr . left . kind !== SyntaxKind . PropertyAccessExpression ) {
1508- return SpecialPropertyAssignmentKind . None ;
1611+ const lhs = expr . left ;
1612+ if ( lhs . expression . kind === SyntaxKind . ThisKeyword ) {
1613+ return SpecialPropertyAssignmentKind . ThisProperty ;
15091614 }
1510- const lhs = < PropertyAccessExpression > expr . left ;
1511- if ( lhs . expression . kind === SyntaxKind . Identifier ) {
1512- const lhsId = < Identifier > lhs . expression ;
1513- if ( lhsId . escapedText === "exports" ) {
1514- // exports.name = expr
1515- return SpecialPropertyAssignmentKind . ExportsProperty ;
1615+ else if ( isIdentifier ( lhs . expression ) && lhs . expression . escapedText === "module" && lhs . name . escapedText === "exports" ) {
1616+ // module.exports = expr
1617+ return SpecialPropertyAssignmentKind . ModuleExports ;
1618+ }
1619+ else if ( isEntityNameExpression ( lhs . expression ) ) {
1620+ if ( lhs . name . escapedText === "prototype" && isObjectLiteralExpression ( expr . right ) ) {
1621+ // F.prototype = { ... }
1622+ return SpecialPropertyAssignmentKind . Prototype ;
15161623 }
1517- else if ( lhsId . escapedText === "module" && lhs . name . escapedText === "exports " ) {
1518- // module.exports = expr
1519- return SpecialPropertyAssignmentKind . ModuleExports ;
1624+ else if ( isPropertyAccessExpression ( lhs . expression ) && lhs . expression . name . escapedText === "prototype " ) {
1625+ // F.G....prototype.x = expr
1626+ return SpecialPropertyAssignmentKind . PrototypeProperty ;
15201627 }
1521- else {
1522- // F.x = expr
1523- return SpecialPropertyAssignmentKind . Property ;
1628+
1629+ let nextToLast = lhs ;
1630+ while ( isPropertyAccessExpression ( nextToLast . expression ) ) {
1631+ nextToLast = nextToLast . expression ;
15241632 }
1525- }
1526- else if ( lhs . expression . kind === SyntaxKind . ThisKeyword ) {
1527- return SpecialPropertyAssignmentKind . ThisProperty ;
1528- }
1529- else if ( lhs . expression . kind === SyntaxKind . PropertyAccessExpression ) {
1530- // chained dot, e.g. x.y.z = expr; this var is the 'x.y' part
1531- const innerPropertyAccess = < PropertyAccessExpression > lhs . expression ;
1532- if ( innerPropertyAccess . expression . kind === SyntaxKind . Identifier ) {
1533- // module.exports.name = expr
1534- const innerPropertyAccessIdentifier = < Identifier > innerPropertyAccess . expression ;
1535- if ( innerPropertyAccessIdentifier . escapedText === "module" && innerPropertyAccess . name . escapedText === "exports" ) {
1536- return SpecialPropertyAssignmentKind . ExportsProperty ;
1537- }
1538- if ( innerPropertyAccess . name . escapedText === "prototype" ) {
1539- return SpecialPropertyAssignmentKind . PrototypeProperty ;
1540- }
1633+ Debug . assert ( isIdentifier ( nextToLast . expression ) ) ;
1634+ const id = nextToLast . expression as Identifier ;
1635+ if ( id . escapedText === "exports" ||
1636+ id . escapedText === "module" && nextToLast . name . escapedText === "exports" ) {
1637+ // exports.name = expr OR module.exports.name = expr
1638+ return SpecialPropertyAssignmentKind . ExportsProperty ;
15411639 }
1640+ // F.G...x = expr
1641+ return SpecialPropertyAssignmentKind . Property ;
15421642 }
15431643
1544-
15451644 return SpecialPropertyAssignmentKind . None ;
15461645 }
15471646
@@ -1617,6 +1716,15 @@ namespace ts {
16171716 node . expression . right ;
16181717 }
16191718
1719+ function getSourceOfDefaultedAssignment ( node : Node ) : Node {
1720+ return isExpressionStatement ( node ) &&
1721+ isBinaryExpression ( node . expression ) &&
1722+ getSpecialPropertyAssignmentKind ( node . expression ) !== SpecialPropertyAssignmentKind . None &&
1723+ isBinaryExpression ( node . expression . right ) &&
1724+ node . expression . right . operatorToken . kind === SyntaxKind . BarBarToken &&
1725+ node . expression . right . right ;
1726+ }
1727+
16201728 function getSingleInitializerOfVariableStatementOrPropertyDeclaration ( node : Node ) : Expression | undefined {
16211729 switch ( node . kind ) {
16221730 case SyntaxKind . VariableStatement :
@@ -1660,7 +1768,8 @@ namespace ts {
16601768 ( getSingleVariableOfVariableStatement ( parent . parent ) === node || getSourceOfAssignment ( parent . parent ) ) ) {
16611769 getJSDocCommentsAndTagsWorker ( parent . parent ) ;
16621770 }
1663- if ( parent && parent . parent && parent . parent . parent && getSingleInitializerOfVariableStatementOrPropertyDeclaration ( parent . parent . parent ) === node ) {
1771+ if ( parent && parent . parent && parent . parent . parent &&
1772+ ( getSingleInitializerOfVariableStatementOrPropertyDeclaration ( parent . parent . parent ) === node || getSourceOfDefaultedAssignment ( parent . parent . parent ) ) ) {
16641773 getJSDocCommentsAndTagsWorker ( parent . parent . parent ) ;
16651774 }
16661775 if ( isBinaryExpression ( node ) && getSpecialPropertyAssignmentKind ( node ) !== SpecialPropertyAssignmentKind . None ||
@@ -1702,7 +1811,8 @@ namespace ts {
17021811
17031812 export function getHostSignatureFromJSDoc ( node : JSDocParameterTag ) : FunctionLike | undefined {
17041813 const host = getJSDocHost ( node ) ;
1705- const decl = getSourceOfAssignment ( host ) ||
1814+ const decl = getSourceOfDefaultedAssignment ( host ) ||
1815+ getSourceOfAssignment ( host ) ||
17061816 getSingleInitializerOfVariableStatementOrPropertyDeclaration ( host ) ||
17071817 getSingleVariableOfVariableStatement ( host ) ||
17081818 getNestedModuleDeclaration ( host ) ||
@@ -1843,6 +1953,16 @@ namespace ts {
18431953 return walkUp ( node , SyntaxKind . ParenthesizedExpression ) ;
18441954 }
18451955
1956+ export function skipParentheses ( node : Expression ) : Expression ;
1957+ export function skipParentheses ( node : Node ) : Node ;
1958+ export function skipParentheses ( node : Node ) : Node {
1959+ while ( node . kind === SyntaxKind . ParenthesizedExpression ) {
1960+ node = ( < ParenthesizedExpression > node ) . expression ;
1961+ }
1962+
1963+ return node ;
1964+ }
1965+
18461966 // a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped
18471967 export function isDeleteTarget ( node : Node ) : boolean {
18481968 if ( node . kind !== SyntaxKind . PropertyAccessExpression && node . kind !== SyntaxKind . ElementAccessExpression ) {
0 commit comments