@@ -1472,13 +1472,115 @@ namespace ts {
1472
1472
}
1473
1473
1474
1474
/**
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 { }
1477
1569
*/
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 ) ;
1482
1584
}
1483
1585
return false ;
1484
1586
}
@@ -1501,47 +1603,44 @@ namespace ts {
1501
1603
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
1502
1604
/// assignments we treat as special in the binder
1503
1605
export function getSpecialPropertyAssignmentKind ( expr : BinaryExpression ) : SpecialPropertyAssignmentKind {
1504
- if ( ! isInJavaScriptFile ( expr ) ) {
1606
+ if ( ! isInJavaScriptFile ( expr ) ||
1607
+ expr . operatorToken . kind !== SyntaxKind . EqualsToken ||
1608
+ ! isPropertyAccessExpression ( expr . left ) ) {
1505
1609
return SpecialPropertyAssignmentKind . None ;
1506
1610
}
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 ;
1509
1614
}
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 ;
1516
1623
}
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 ;
1520
1627
}
1521
- else {
1522
- // F.x = expr
1523
- return SpecialPropertyAssignmentKind . Property ;
1628
+
1629
+ let nextToLast = lhs ;
1630
+ while ( isPropertyAccessExpression ( nextToLast . expression ) ) {
1631
+ nextToLast = nextToLast . expression ;
1524
1632
}
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 ;
1541
1639
}
1640
+ // F.G...x = expr
1641
+ return SpecialPropertyAssignmentKind . Property ;
1542
1642
}
1543
1643
1544
-
1545
1644
return SpecialPropertyAssignmentKind . None ;
1546
1645
}
1547
1646
@@ -1617,6 +1716,15 @@ namespace ts {
1617
1716
node . expression . right ;
1618
1717
}
1619
1718
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
+
1620
1728
function getSingleInitializerOfVariableStatementOrPropertyDeclaration ( node : Node ) : Expression | undefined {
1621
1729
switch ( node . kind ) {
1622
1730
case SyntaxKind . VariableStatement :
@@ -1660,7 +1768,8 @@ namespace ts {
1660
1768
( getSingleVariableOfVariableStatement ( parent . parent ) === node || getSourceOfAssignment ( parent . parent ) ) ) {
1661
1769
getJSDocCommentsAndTagsWorker ( parent . parent ) ;
1662
1770
}
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 ) ) ) {
1664
1773
getJSDocCommentsAndTagsWorker ( parent . parent . parent ) ;
1665
1774
}
1666
1775
if ( isBinaryExpression ( node ) && getSpecialPropertyAssignmentKind ( node ) !== SpecialPropertyAssignmentKind . None ||
@@ -1702,7 +1811,8 @@ namespace ts {
1702
1811
1703
1812
export function getHostSignatureFromJSDoc ( node : JSDocParameterTag ) : FunctionLike | undefined {
1704
1813
const host = getJSDocHost ( node ) ;
1705
- const decl = getSourceOfAssignment ( host ) ||
1814
+ const decl = getSourceOfDefaultedAssignment ( host ) ||
1815
+ getSourceOfAssignment ( host ) ||
1706
1816
getSingleInitializerOfVariableStatementOrPropertyDeclaration ( host ) ||
1707
1817
getSingleVariableOfVariableStatement ( host ) ||
1708
1818
getNestedModuleDeclaration ( host ) ||
@@ -1843,6 +1953,16 @@ namespace ts {
1843
1953
return walkUp ( node , SyntaxKind . ParenthesizedExpression ) ;
1844
1954
}
1845
1955
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
+
1846
1966
// a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped
1847
1967
export function isDeleteTarget ( node : Node ) : boolean {
1848
1968
if ( node . kind !== SyntaxKind . PropertyAccessExpression && node . kind !== SyntaxKind . ElementAccessExpression ) {
0 commit comments