Skip to content

Commit 99d866d

Browse files
authored
Merge pull request #21974 from Microsoft/js-object-literal-assignments-as-declarations
More special declaration types in JS
2 parents 9586288 + f8134d0 commit 99d866d

File tree

68 files changed

+5904
-9109
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+5904
-9109
lines changed

src/compiler/binder.ts

Lines changed: 115 additions & 70 deletions
Large diffs are not rendered by default.

src/compiler/checker.ts

Lines changed: 100 additions & 44 deletions
Large diffs are not rendered by default.

src/compiler/factory.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4266,16 +4266,6 @@ namespace ts {
42664266
return node;
42674267
}
42684268

4269-
export function skipParentheses(node: Expression): Expression;
4270-
export function skipParentheses(node: Node): Node;
4271-
export function skipParentheses(node: Node): Node {
4272-
while (node.kind === SyntaxKind.ParenthesizedExpression) {
4273-
node = (<ParenthesizedExpression>node).expression;
4274-
}
4275-
4276-
return node;
4277-
}
4278-
42794269
export function skipAssertions(node: Expression): Expression;
42804270
export function skipAssertions(node: Node): Node;
42814271
export function skipAssertions(node: Node): Node {

src/compiler/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3260,8 +3260,8 @@ namespace ts {
32603260

32613261
Enum = RegularEnum | ConstEnum,
32623262
Variable = FunctionScopedVariable | BlockScopedVariable,
3263-
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor,
3264-
Type = Class | Interface | Enum | EnumMember | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias,
3263+
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | JSContainer,
3264+
Type = Class | Interface | Enum | EnumMember | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias | JSContainer,
32653265
Namespace = ValueModule | NamespaceModule | Enum,
32663266
Module = ValueModule | NamespaceModule,
32673267
Accessor = GetAccessor | SetAccessor,
@@ -4000,7 +4000,9 @@ namespace ts {
40004000
/// this.name = expr
40014001
ThisProperty,
40024002
// F.name = expr
4003-
Property
4003+
Property,
4004+
// F.prototype = { ... }
4005+
Prototype,
40044006
}
40054007

40064008
export interface JsFileExtensionInfo {

src/compiler/utilities.ts

Lines changed: 160 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {

src/harness/fourslash.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ namespace FourSlash {
848848
const actual = actualCompletions.entries;
849849

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

854854
ts.zipWith(actual, expected, (completion, expectedCompletion, index) => {

src/services/codefixes/convertFunctionToEs6Class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,4 @@ namespace ts.codefix {
221221
function getModifierKindFromSource(source: Node, kind: SyntaxKind): ReadonlyArray<Modifier> {
222222
return filter(source.modifiers, modifier => modifier.kind === kind);
223223
}
224-
}
224+
}

src/services/navigationBar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ namespace ts.NavigationBar {
275275
case SpecialPropertyAssignmentKind.ExportsProperty:
276276
case SpecialPropertyAssignmentKind.ModuleExports:
277277
case SpecialPropertyAssignmentKind.PrototypeProperty:
278+
case SpecialPropertyAssignmentKind.Prototype:
278279
addNodeWithRecursiveChild(node, (node as BinaryExpression).right);
279280
break;
280281
case SpecialPropertyAssignmentKind.ThisProperty:

src/services/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,8 @@ namespace ts {
359359
case SpecialPropertyAssignmentKind.Property:
360360
// static method / property
361361
return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement;
362+
case SpecialPropertyAssignmentKind.Prototype:
363+
return ScriptElementKind.localClassElement;
362364
default: {
363365
assertTypeIsNever(kind);
364366
return ScriptElementKind.unknown;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,28 +1960,28 @@ declare namespace ts {
19601960
JSContainer = 67108864,
19611961
Enum = 384,
19621962
Variable = 3,
1963-
Value = 107455,
1964-
Type = 793064,
1963+
Value = 67216319,
1964+
Type = 67901928,
19651965
Namespace = 1920,
19661966
Module = 1536,
19671967
Accessor = 98304,
1968-
FunctionScopedVariableExcludes = 107454,
1969-
BlockScopedVariableExcludes = 107455,
1970-
ParameterExcludes = 107455,
1968+
FunctionScopedVariableExcludes = 67216318,
1969+
BlockScopedVariableExcludes = 67216319,
1970+
ParameterExcludes = 67216319,
19711971
PropertyExcludes = 0,
1972-
EnumMemberExcludes = 900095,
1973-
FunctionExcludes = 106927,
1974-
ClassExcludes = 899519,
1975-
InterfaceExcludes = 792968,
1976-
RegularEnumExcludes = 899327,
1977-
ConstEnumExcludes = 899967,
1978-
ValueModuleExcludes = 106639,
1972+
EnumMemberExcludes = 68008959,
1973+
FunctionExcludes = 67215791,
1974+
ClassExcludes = 68008383,
1975+
InterfaceExcludes = 67901832,
1976+
RegularEnumExcludes = 68008191,
1977+
ConstEnumExcludes = 68008831,
1978+
ValueModuleExcludes = 67215503,
19791979
NamespaceModuleExcludes = 0,
1980-
MethodExcludes = 99263,
1981-
GetAccessorExcludes = 41919,
1982-
SetAccessorExcludes = 74687,
1983-
TypeParameterExcludes = 530920,
1984-
TypeAliasExcludes = 793064,
1980+
MethodExcludes = 67208127,
1981+
GetAccessorExcludes = 67150783,
1982+
SetAccessorExcludes = 67183551,
1983+
TypeParameterExcludes = 67639784,
1984+
TypeAliasExcludes = 67901928,
19851985
AliasExcludes = 2097152,
19861986
ModuleMember = 2623475,
19871987
ExportHasLocal = 944,

0 commit comments

Comments
 (0)