Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d2c0ec7
JS:resolve nested static classes+on func/class exps
sandersn Nov 21, 2017
b1c735f
Test:Type references to nested JS classes
sandersn Nov 21, 2017
418274e
Merge branch 'master' into jsdoc-values-as-namespaces
sandersn Nov 21, 2017
1d1a7d8
Update chrome-devtools baseline
sandersn Nov 21, 2017
082802e
JS:Treat type-annotated uninitialised vars as declarations
sandersn Nov 21, 2017
fa96bd4
More tests and update baselines
sandersn Nov 21, 2017
2f0e581
JS Property assignments create namespaces (hacky)
sandersn Nov 21, 2017
d4f6c8c
Merge branch 'js-ctor-props-type-annotation-as-declaration' into jsdo…
sandersn Nov 21, 2017
bee7d06
Binder-based prop-assignment decls: messy version
sandersn Nov 22, 2017
ee5f91c
Update baselines
sandersn Nov 22, 2017
9b8b750
Always put assignments in locals.
sandersn Nov 27, 2017
4199038
Fix type-annotation declaration in JS files
sandersn Nov 27, 2017
d338ecd
Tests:more JS static property assignments
sandersn Nov 27, 2017
3a74147
Add empty 'module' declarations for chrome-devtools
sandersn Nov 27, 2017
5024aa6
Update chrome-devtools baseline
sandersn Nov 27, 2017
33f3e49
Update chrome dev tools definitions
sandersn Nov 28, 2017
74faa3d
JS static properties:fix multi-file references+merging
sandersn Nov 28, 2017
e441dd0
Binder:clean up bindPropertyAssignment and friends
sandersn Nov 28, 2017
61fe04b
Merge branch 'master' into jsdoc-values-as-namespaces
sandersn Nov 28, 2017
341e702
JS static prop assignments don't need same type
sandersn Nov 28, 2017
78a0b94
Update chrome devtools baseline
sandersn Nov 28, 2017
098a052
Remove temporary error-avoidance hack
sandersn Nov 28, 2017
665c2ec
Add SymbolFlag for containers of JS special decls
sandersn Nov 30, 2017
69bbfed
Merge branch 'master' into jsdoc-values-as-namespaces
sandersn Nov 30, 2017
0e4537e
Update baselines with new SymbolFlags
sandersn Nov 30, 2017
7208204
Don't mutate symbol flags after creation
sandersn Nov 30, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 60 additions & 19 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2006,6 +2006,9 @@ namespace ts {
if (currentFlow && isNarrowableReference(<Expression>node)) {
node.flowNode = currentFlow;
}
if (isSpecialPropertyDeclaration(node as PropertyAccessExpression)) {
bindSpecialPropertyDeclaration(node as PropertyAccessExpression);
}
break;
case SyntaxKind.BinaryExpression:
const specialKind = getSpecialPropertyAssignmentKind(node as BinaryExpression);
Expand Down Expand Up @@ -2314,7 +2317,7 @@ namespace ts {
declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule, SymbolFlags.None);
}

function bindThisPropertyAssignment(node: BinaryExpression) {
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
Debug.assert(isInJavaScriptFile(node));
const container = getThisContainer(node, /*includeArrowFunctions*/ false);
switch (container.kind) {
Expand All @@ -2340,8 +2343,19 @@ namespace ts {
}
}

function bindSpecialPropertyDeclaration(node: PropertyAccessExpression) {
Debug.assert(isInJavaScriptFile(node));
if (node.expression.kind === SyntaxKind.ThisKeyword) {
bindThisPropertyAssignment(node);
}
else if ((node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression) &&
node.parent.parent.kind === SyntaxKind.SourceFile) {
bindStaticPropertyAssignment(node);
}
}

function bindPrototypePropertyAssignment(node: BinaryExpression) {
// We saw a node of the form 'x.prototype.y = z'. Declare a 'member' y on x if x was a function.
// We saw a node of the form 'x.prototype.y = z'. Declare a 'member' y on x if x is a function or class, or not declared.

// Look up the function in the local scope, since prototype assignments should
// follow the function declaration
Expand All @@ -2357,24 +2371,27 @@ namespace ts {
bindPropertyAssignment(constructorFunction.escapedText, leftSideOfAssignment, /*isPrototypeProperty*/ true);
}

function bindStaticPropertyAssignment(node: BinaryExpression) {
// We saw a node of the form 'x.y = z'. Declare a 'member' y on x if x was a function.

// Look up the function in the local scope, since prototype assignments should
/**
* For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function or class, or not declared.
* Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y;
*/
function bindStaticPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {
// Look up the function in the local scope, since static assignments should
// follow the function declaration
const leftSideOfAssignment = node.left as PropertyAccessExpression;
const leftSideOfAssignment = node.kind === SyntaxKind.PropertyAccessExpression ? node : node.left as PropertyAccessExpression;
const target = leftSideOfAssignment.expression;

if (isIdentifier(target)) {
// Fix up parent pointers since we're going to use these nodes before we bind into them
leftSideOfAssignment.parent = node;
target.parent = leftSideOfAssignment;

if (node.kind === SyntaxKind.BinaryExpression) {
leftSideOfAssignment.parent = node;
}
if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) {
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
// var util = module.exports;
// util.property = function ...
bindExportsPropertyAssignment(node);
bindExportsPropertyAssignment(node as BinaryExpression);
}
else {
bindPropertyAssignment(target.escapedText, leftSideOfAssignment, /*isPrototypeProperty*/ false);
Expand All @@ -2383,17 +2400,41 @@ namespace ts {
}

function lookupSymbolForName(name: __String) {
return (container.symbol && container.symbol.exports && container.symbol.exports.get(name)) || (container.locals && container.locals.get(name));
const local = container.locals && container.locals.get(name);
if (local) {
return local.exportSymbol || local;
}
return container.symbol && container.symbol.exports && container.symbol.exports.get(name);
}

function bindPropertyAssignment(functionName: __String, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
let targetSymbol = lookupSymbolForName(functionName);

if (targetSymbol && isDeclarationOfFunctionOrClassExpression(targetSymbol)) {
targetSymbol = (targetSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
function bindPropertyAssignment(functionName: __String, propertyAccess: PropertyAccessExpression, isPrototypeProperty: boolean) {
const symbol = lookupSymbolForName(functionName);
let targetSymbol = symbol && isDeclarationOfFunctionOrClassExpression(symbol) ?
(symbol.valueDeclaration as VariableDeclaration).initializer.symbol :
symbol;
Debug.assert(propertyAccess.parent.kind === SyntaxKind.BinaryExpression || propertyAccess.parent.kind === SyntaxKind.ExpressionStatement);
let isLegalPosition: boolean;
if (propertyAccess.parent.kind === SyntaxKind.BinaryExpression) {
const initializerKind = (propertyAccess.parent as BinaryExpression).right.kind;
isLegalPosition = (initializerKind === SyntaxKind.ClassExpression || initializerKind === SyntaxKind.FunctionExpression) &&
propertyAccess.parent.parent.parent.kind === SyntaxKind.SourceFile;
}

if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class))) {
else {
isLegalPosition = propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
}
if (!isPrototypeProperty && (!targetSymbol || !(targetSymbol.flags & SymbolFlags.Namespace)) && isLegalPosition) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to handle these recursivelly as well.. i.e. today we handel:

var outer;
outer = function() {};
outer.inner = function () {};

but we should also handle:

var outer;
outer = function() {};
outer.inner = function () {};
outer.inner.another = function () {};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I will do that in a follow-up PR. It will require touching even more of the existing code.

Debug.assert(isIdentifier(propertyAccess.expression));
const identifier = propertyAccess.expression as Identifier;
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
if (targetSymbol) {
addDeclarationToSymbol(symbol, identifier, flags);
}
else {
targetSymbol = declareSymbol(container.locals, /*parent*/ undefined, identifier, flags, excludeFlags);
}
}
if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule))) {
return;
}

Expand All @@ -2403,7 +2444,7 @@ namespace ts {
(targetSymbol.exports || (targetSymbol.exports = createSymbolTable()));

// Declare the method/property
declareSymbol(symbolTable, targetSymbol, propertyAccessExpression, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
declareSymbol(symbolTable, targetSymbol, propertyAccess, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
}

function bindCallExpression(node: CallExpression) {
Expand Down
18 changes: 14 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,9 @@ namespace ts {
}

function mergeSymbol(target: Symbol, source: Symbol) {
if (!(target.flags & getExcludedSymbolFlags(source.flags))) {
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
source.flags & SymbolFlags.JSContainer || target.flags & SymbolFlags.JSContainer) {
// Javascript static-property-assignment declarations always merge, even though they are also values
if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) {
// reset flag when merging instantiated module into value module that has only const enums
target.constEnumOnlyModule = false;
Expand Down Expand Up @@ -1735,13 +1737,16 @@ namespace ts {
return undefined;
}
const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name;
const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors, /*dontResolveAlias*/ false, location);
let namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors, /*dontResolveAlias*/ false, location);
if (!namespace || nodeIsMissing(right)) {
return undefined;
}
else if (namespace === unknownSymbol) {
return namespace;
}
if (isInJavaScriptFile(name) && isDeclarationOfFunctionOrClassExpression(namespace)) {
namespace = getSymbolOfNode((namespace.valueDeclaration as VariableDeclaration).initializer);
}
symbol = getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning);
if (!symbol) {
if (!ignoreErrors) {
Expand Down Expand Up @@ -4478,7 +4483,9 @@ namespace ts {
if (!jsDocType) {
jsDocType = declarationType;
}
else if (jsDocType !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(jsDocType, declarationType)) {
else if (jsDocType !== unknownType && declarationType !== unknownType &&
!isTypeIdenticalTo(jsDocType, declarationType) &&
!(symbol.flags & SymbolFlags.JSContainer)) {
errorNextVariableOrPropertyDeclarationMustHaveSameType(jsDocType, declaration, declarationType);
}
}
Expand Down Expand Up @@ -21443,7 +21450,10 @@ namespace ts {
// Node is a secondary declaration, check that type is identical to primary declaration and check that
// initializer is consistent with type associated with the node
const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node));
if (type !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(type, declarationType)) {

if (type !== unknownType && declarationType !== unknownType &&
!isTypeIdenticalTo(type, declarationType) &&
!(symbol.flags & SymbolFlags.JSContainer)) {
errorNextVariableOrPropertyDeclarationMustHaveSameType(type, node, declarationType);
}
if (node.initializer) {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ namespace ts {
Node, // Unique name based on the node in the 'original' property.
}

export interface Identifier extends PrimaryExpression {
export interface Identifier extends PrimaryExpression, Declaration {
kind: SyntaxKind.Identifier;
/**
* Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
Expand Down Expand Up @@ -3062,6 +3062,7 @@ namespace ts {
ExportStar = 1 << 23, // Export * declaration
Optional = 1 << 24, // Optional property
Transient = 1 << 25, // Transient symbol (created during type check)
JSContainer = 1 << 26, // Contains Javascript special declarations

/* @internal */
All = FunctionScopedVariable | BlockScopedVariable | Property | EnumMember | Function | Class | Interface | ConstEnum | RegularEnum | ValueModule | NamespaceModule | TypeLiteral
Expand Down
13 changes: 11 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ namespace ts {
}

/**
* Returns true if the node is a variable declaration whose initializer is a function expression.
* Returns true if the node is a variable declaration whose initializer is a function or class expression.
* This function does not test if the node is in a JavaScript file or not.
*/
export function isDeclarationOfFunctionOrClassExpression(s: Symbol) {
Expand Down Expand Up @@ -1519,6 +1519,12 @@ namespace ts {
return SpecialPropertyAssignmentKind.None;
}

export function isSpecialPropertyDeclaration(expr: ts.PropertyAccessExpression): boolean {
return isInJavaScriptFile(expr) &&
expr.parent && expr.parent.kind === SyntaxKind.ExpressionStatement &&
!!getJSDocTypeTag(expr.parent);
}

export function getExternalModuleName(node: Node): Expression {
if (node.kind === SyntaxKind.ImportDeclaration) {
return (<ImportDeclaration>node).moduleSpecifier;
Expand Down Expand Up @@ -1636,7 +1642,8 @@ namespace ts {
if (parent && parent.parent && parent.parent.parent && getSingleInitializerOfVariableStatement(parent.parent.parent, node)) {
getJSDocCommentsAndTagsWorker(parent.parent.parent);
}
if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None) {
if (isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) !== SpecialPropertyAssignmentKind.None ||
node.kind === SyntaxKind.PropertyAccessExpression && node.parent && node.parent.kind === SyntaxKind.ExpressionStatement) {
getJSDocCommentsAndTagsWorker(parent);
}

Expand Down Expand Up @@ -4203,6 +4210,8 @@ namespace ts {
return undefined;
}
switch (declaration.kind) {
case SyntaxKind.Identifier:
return declaration as Identifier;
case SyntaxKind.JSDocPropertyTag:
case SyntaxKind.JSDocParameterTag: {
const { name } = declaration as JSDocPropertyLikeTag;
Expand Down
1 change: 1 addition & 0 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ namespace ts {
_updateExpressionBrand: any;
_unaryExpressionBrand: any;
_expressionBrand: any;
_declarationBrand: any;
/*@internal*/typeArguments: NodeArray<TypeNode>;
constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) {
super(pos, end);
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ declare namespace ts {
type AwaitKeywordToken = Token<SyntaxKind.AwaitKeyword>;
type Modifier = Token<SyntaxKind.AbstractKeyword> | Token<SyntaxKind.AsyncKeyword> | Token<SyntaxKind.ConstKeyword> | Token<SyntaxKind.DeclareKeyword> | Token<SyntaxKind.DefaultKeyword> | Token<SyntaxKind.ExportKeyword> | Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword> | Token<SyntaxKind.ProtectedKeyword> | Token<SyntaxKind.ReadonlyKeyword> | Token<SyntaxKind.StaticKeyword>;
type ModifiersArray = NodeArray<Modifier>;
interface Identifier extends PrimaryExpression {
interface Identifier extends PrimaryExpression, Declaration {
kind: SyntaxKind.Identifier;
/**
* Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
Expand Down Expand Up @@ -1903,6 +1903,7 @@ declare namespace ts {
ExportStar = 8388608,
Optional = 16777216,
Transient = 33554432,
JSContainer = 67108864,
Enum = 384,
Variable = 3,
Value = 107455,
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ declare namespace ts {
type AwaitKeywordToken = Token<SyntaxKind.AwaitKeyword>;
type Modifier = Token<SyntaxKind.AbstractKeyword> | Token<SyntaxKind.AsyncKeyword> | Token<SyntaxKind.ConstKeyword> | Token<SyntaxKind.DeclareKeyword> | Token<SyntaxKind.DefaultKeyword> | Token<SyntaxKind.ExportKeyword> | Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword> | Token<SyntaxKind.ProtectedKeyword> | Token<SyntaxKind.ReadonlyKeyword> | Token<SyntaxKind.StaticKeyword>;
type ModifiersArray = NodeArray<Modifier>;
interface Identifier extends PrimaryExpression {
interface Identifier extends PrimaryExpression, Declaration {
kind: SyntaxKind.Identifier;
/**
* Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.)
Expand Down Expand Up @@ -1903,6 +1903,7 @@ declare namespace ts {
ExportStar = 8388608,
Optional = 16777216,
Transient = 33554432,
JSContainer = 67108864,
Enum = 384,
Variable = 3,
Value = 107455,
Expand Down
41 changes: 41 additions & 0 deletions tests/baselines/reference/typeFromPropertyAssignment.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
=== tests/cases/conformance/salsa/a.js ===
var Outer = class O {
>Outer : Symbol(Outer, Decl(a.js, 0, 3), Decl(a.js, 2, 1))
>O : Symbol(O, Decl(a.js, 0, 11))

m(x, y) { }
>m : Symbol(O.m, Decl(a.js, 0, 21))
>x : Symbol(x, Decl(a.js, 1, 6))
>y : Symbol(y, Decl(a.js, 1, 8))
}
Outer.Inner = class I {
>Outer.Inner : Symbol(O.Inner, Decl(a.js, 2, 1))
>Outer : Symbol(Outer, Decl(a.js, 0, 3), Decl(a.js, 2, 1))
>Inner : Symbol(O.Inner, Decl(a.js, 2, 1))
>I : Symbol(I, Decl(a.js, 3, 13))

n(a, b) { }
>n : Symbol(I.n, Decl(a.js, 3, 23))
>a : Symbol(a, Decl(a.js, 4, 6))
>b : Symbol(b, Decl(a.js, 4, 8))

}
/** @type {Outer} */
var si
>si : Symbol(si, Decl(a.js, 8, 3))

si.m
>si.m : Symbol(O.m, Decl(a.js, 0, 21))
>si : Symbol(si, Decl(a.js, 8, 3))
>m : Symbol(O.m, Decl(a.js, 0, 21))

/** @type {Outer.Inner} */
var oi
>oi : Symbol(oi, Decl(a.js, 11, 3))

oi.n
>oi.n : Symbol(I.n, Decl(a.js, 3, 23))
>oi : Symbol(oi, Decl(a.js, 11, 3))
>n : Symbol(I.n, Decl(a.js, 3, 23))


44 changes: 44 additions & 0 deletions tests/baselines/reference/typeFromPropertyAssignment.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=== tests/cases/conformance/salsa/a.js ===
var Outer = class O {
>Outer : typeof O
>class O { m(x, y) { }} : typeof O
>O : typeof O

m(x, y) { }
>m : (x: any, y: any) => void
>x : any
>y : any
}
Outer.Inner = class I {
>Outer.Inner = class I { n(a, b) { }} : typeof I
>Outer.Inner : typeof I
>Outer : typeof O
>Inner : typeof I
>class I { n(a, b) { }} : typeof I
>I : typeof I

n(a, b) { }
>n : (a: any, b: any) => void
>a : any
>b : any

}
/** @type {Outer} */
var si
>si : O

si.m
>si.m : (x: any, y: any) => void
>si : O
>m : (x: any, y: any) => void

/** @type {Outer.Inner} */
var oi
>oi : I

oi.n
>oi.n : (a: any, b: any) => void
>oi : I
>n : (a: any, b: any) => void


Loading