Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 14 additions & 5 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,7 @@ namespace ts {
// Export assignment in some sort of block construct
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
}
else if (boundExpression.kind === SyntaxKind.Identifier) {
else if (boundExpression.kind === SyntaxKind.Identifier && node.kind === SyntaxKind.ExportAssignment) {
// An export default clause with an identifier exports all meanings of that identifier
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Alias, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
}
Expand Down Expand Up @@ -1435,7 +1435,8 @@ namespace ts {
// Declare a 'member' in case it turns out the container was an ES5 class
if (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.FunctionDeclaration) {
container.symbol.members = container.symbol.members || {};
declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
// It's acceptable for multiple 'this' assignments of the same identifier to occur
declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property);
}
}

Expand All @@ -1444,8 +1445,16 @@ namespace ts {

// Look up the function in the local scope, since prototype assignments should
// follow the function declaration
const classId = <Identifier>(<PropertyAccessExpression>(<PropertyAccessExpression>node.left).expression).expression;
const funcSymbol = container.locals[classId.text];
const leftSideOfAssignment = node.left as PropertyAccessExpression;
const classPrototype = leftSideOfAssignment.expression as PropertyAccessExpression;
const constructorFunction = classPrototype.expression as Identifier;

// Fix up parent pointers since we're going to use these nodes before we bind into them
leftSideOfAssignment.parent = node;
constructorFunction.parent = classPrototype;
classPrototype.parent = leftSideOfAssignment;

const funcSymbol = container.locals[constructorFunction.text];
if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function)) {
return;
}
Expand All @@ -1456,7 +1465,7 @@ namespace ts {
}

// Declare the method/property
declareSymbol(funcSymbol.members, funcSymbol, <PropertyAccessExpression>node.left, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
}

function bindCallExpression(node: CallExpression) {
Expand Down
9 changes: 6 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2845,7 +2845,7 @@ namespace ts {
}
// Handle module.exports = expr
if (declaration.kind === SyntaxKind.BinaryExpression) {
return links.type = checkExpression((<BinaryExpression>declaration).right);
return links.type = getUnionType(map(symbol.declarations, (decl: BinaryExpression) => checkExpressionCached(decl.right)));
}
if (declaration.kind === SyntaxKind.PropertyAccessExpression) {
// Declarations only exist for property access expressions for certain
Expand Down Expand Up @@ -10356,9 +10356,11 @@ namespace ts {

function getReturnTypeFromJSDocComment(func: SignatureDeclaration | FunctionDeclaration): Type {
const returnTag = getJSDocReturnTag(func);
if (returnTag) {
if (returnTag && returnTag.typeExpression) {
return getTypeFromTypeNode(returnTag.typeExpression.type);
}

return undefined;
}

function createPromiseType(promisedType: Type): Type {
Expand Down Expand Up @@ -10433,7 +10435,8 @@ namespace ts {
}
else {
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
return unknownType;
// Defer to unioning the return types so we get a) downstream errors earlier and b) better Salsa experience
return getUnionType(types);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4051,7 +4051,7 @@ namespace ts {
setDecoratorContext(/*val*/ true);
}

return finishNode(node);
return addJSDocComment(finishNode(node));
}

function parseOptionalIdentifier() {
Expand Down Expand Up @@ -4335,13 +4335,13 @@ namespace ts {
const labeledStatement = <LabeledStatement>createNode(SyntaxKind.LabeledStatement, fullStart);
labeledStatement.label = <Identifier>expression;
labeledStatement.statement = parseStatement();
return finishNode(labeledStatement);
return addJSDocComment(finishNode(labeledStatement));
}
else {
const expressionStatement = <ExpressionStatement>createNode(SyntaxKind.ExpressionStatement, fullStart);
expressionStatement.expression = expression;
parseSemicolon();
return finishNode(expressionStatement);
return addJSDocComment(finishNode(expressionStatement));
}
}

Expand Down Expand Up @@ -4778,7 +4778,7 @@ namespace ts {
parseExpected(SyntaxKind.ConstructorKeyword);
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ false, /*awaitContext*/ false, /*requireCompleteParameterList*/ false, node);
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, /*isAsync*/ false, Diagnostics.or_expected);
return finishNode(node);
return addJSDocComment(finishNode(node));
}

function parseMethodDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray, asteriskToken: Node, name: PropertyName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration {
Expand All @@ -4792,7 +4792,7 @@ namespace ts {
const isAsync = !!(method.flags & NodeFlags.Async);
fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, method);
method.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, diagnosticMessage);
return finishNode(method);
return addJSDocComment(finishNode(method));
}

function parsePropertyDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray, name: PropertyName, questionToken: Node): ClassElement {
Expand Down
14 changes: 13 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,19 @@ namespace ts {
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;

const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent : undefined;
return variableStatementNode && variableStatementNode.jsDocComment;
if (variableStatementNode) {
return variableStatementNode.jsDocComment;
}

// Also recognize when the node is the RHS of an assignment expression
const isSourceOfAssignmentExpressionStatement =
node.parent && node.parent.parent &&
node.parent.kind === SyntaxKind.BinaryExpression &&
(node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
node.parent.parent.kind === SyntaxKind.ExpressionStatement;
if (isSourceOfAssignmentExpressionStatement) {
return node.parent.parent.jsDocComment;
}
}

return undefined;
Expand Down
25 changes: 14 additions & 11 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ namespace ts {
export interface SourceFile {
/* @internal */ version: string;
/* @internal */ scriptSnapshot: IScriptSnapshot;
/* @internal */ nameTable: Map<string>;
/* @internal */ nameTable: Map<number>;

/* @internal */ getNamedDeclarations(): Map<Declaration[]>;

Expand Down Expand Up @@ -808,7 +808,7 @@ namespace ts {
public languageVersion: ScriptTarget;
public languageVariant: LanguageVariant;
public identifiers: Map<string>;
public nameTable: Map<string>;
public nameTable: Map<number>;
public resolvedModules: Map<ResolvedModule>;
public imports: LiteralExpression[];
public moduleAugmentations: LiteralExpression[];
Expand Down Expand Up @@ -1957,8 +1957,6 @@ namespace ts {
const text = scriptSnapshot.getText(0, scriptSnapshot.getLength());
const sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents);
setSourceFileFields(sourceFile, scriptSnapshot, version);
// after full parsing we can use table with interned strings as name table
sourceFile.nameTable = sourceFile.identifiers;
return sourceFile;
}

Expand Down Expand Up @@ -3834,7 +3832,7 @@ namespace ts {

if (isRightOfDot && isSourceFileJavaScript(sourceFile)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries);
addRange(entries, getJavaScriptCompletionEntries(sourceFile, uniqueNames));
addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames));
}
else {
if (!symbols || symbols.length === 0) {
Expand Down Expand Up @@ -3867,12 +3865,17 @@ namespace ts {

return { isMemberCompletion, isNewIdentifierLocation, entries };

function getJavaScriptCompletionEntries(sourceFile: SourceFile, uniqueNames: Map<string>): CompletionEntry[] {
function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: Map<string>): CompletionEntry[] {
const entries: CompletionEntry[] = [];
const target = program.getCompilerOptions().target;

const nameTable = getNameTable(sourceFile);
for (const name in nameTable) {
// Skip identifiers produced only from the current location
if (nameTable[name] === position) {
continue;
}

if (!uniqueNames[name]) {
uniqueNames[name] = name;
const displayName = getCompletionEntryDisplayName(name, target, /*performCharacterChecks*/ true);
Expand Down Expand Up @@ -5487,7 +5490,7 @@ namespace ts {

const nameTable = getNameTable(sourceFile);

if (lookUp(nameTable, internedName)) {
if (lookUp(nameTable, internedName) !== undefined) {
result = result || [];
getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex);
}
Expand Down Expand Up @@ -7525,7 +7528,7 @@ namespace ts {
}

/* @internal */
export function getNameTable(sourceFile: SourceFile): Map<string> {
export function getNameTable(sourceFile: SourceFile): Map<number> {
if (!sourceFile.nameTable) {
initializeNameTable(sourceFile);
}
Expand All @@ -7534,15 +7537,15 @@ namespace ts {
}

function initializeNameTable(sourceFile: SourceFile): void {
const nameTable: Map<string> = {};
const nameTable: Map<number> = {};

walk(sourceFile);
sourceFile.nameTable = nameTable;

function walk(node: Node) {
switch (node.kind) {
case SyntaxKind.Identifier:
nameTable[(<Identifier>node).text] = (<Identifier>node).text;
nameTable[(<Identifier>node).text] = nameTable[(<Identifier>node).text] === undefined ? node.pos : -1;
break;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
Expand All @@ -7554,7 +7557,7 @@ namespace ts {
node.parent.kind === SyntaxKind.ExternalModuleReference ||
isArgumentOfElementAccessExpression(node)) {

nameTable[(<LiteralExpression>node).text] = (<LiteralExpression>node).text;
nameTable[(<LiteralExpression>node).text] = nameTable[(<LiteralExpression>node).text] === undefined ? node.pos : -1;
}
break;
default:
Expand Down
35 changes: 35 additions & 0 deletions tests/cases/fourslash/getJavaScriptCompletions16.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// <reference path="fourslash.ts" />

// @allowNonTsExtensions: true
// @Filename: file.js
//// "use strict";
////
//// class Something {
////
//// /**
//// * @param {number} a
//// */
//// constructor(a, b) {
//// a/*body*/
//// }
////
//// /**
//// * @param {number} a
//// */
//// method(a) {
//// a/*method*/
//// }
//// }
//// let x = new Something(/*sig*/);

goTo.marker('body');
edit.insert('.');
verify.completionListContains('toFixed', undefined, undefined, 'method');
edit.backspace();

goTo.marker('sig');
verify.currentSignatureHelpIs('Something(a: number, b: any): Something');

goTo.marker('method');
edit.insert('.');
verify.completionListContains('toFixed', undefined, undefined, 'method');
21 changes: 21 additions & 0 deletions tests/cases/fourslash/getJavaScriptCompletions18.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path="fourslash.ts" />

// @allowNonTsExtensions: true
// @Filename: file.js
//// /**
//// * @param {number} a
//// * @param {string} b
//// */
//// exports.foo = function(a, b) {
//// a/*a*/;
//// b/*b*/
//// };

goTo.marker('a');
edit.insert('.');
verify.completionListContains('toFixed', undefined, undefined, 'method');


goTo.marker('b');
edit.insert('.');
verify.completionListContains('substr', undefined, undefined, 'method');
25 changes: 25 additions & 0 deletions tests/cases/fourslash/getJavaScriptCompletions19.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// <reference path="fourslash.ts" />

// @allowNonTsExtensions: true
// @Filename: file.js
//// function fn() {
//// if (foo) {
//// return 0;
//// } else {
//// return '0';
//// }
//// }
//// let x = fn();
//// if(typeof x === 'string') {
//// x/*str*/
//// } else {
//// x/*num*/
//// }

goTo.marker('str');
edit.insert('.');
verify.completionListContains('substr', undefined, undefined, 'method');

goTo.marker('num');
edit.insert('.');
verify.completionListContains('toFixed', undefined, undefined, 'method');
21 changes: 21 additions & 0 deletions tests/cases/fourslash/getJavaScriptCompletions20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path="fourslash.ts" />

// @allowNonTsExtensions: true
// @Filename: file.js
//// /**
//// * A person
//// * @constructor
//// * @param {string} name - The name of the person.
//// * @param {number} age - The age of the person.
//// */
//// function Person(name, age) {
//// this.name = name;
//// this.age = age;
//// }
////
////
//// Person.getName = 10;
//// Person.getNa/**/ = 10;

goTo.marker();
verify.not.memberListContains('getNa');
20 changes: 20 additions & 0 deletions tests/cases/fourslash/getJavaScriptQuickInfo7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path="fourslash.ts" />

// @allowNonTsExtensions: true
// @Filename: file.js
//// /**
//// * This is a very cool function that is very nice.
//// * @returns something
//// * @param p anotherthing
//// */
//// function a1(p) {
//// try {
//// throw new Error('x');
//// } catch (x) { x--; }
//// return 23;
//// }
////
//// x - /**/a1()

goTo.marker();
verify.quickInfoExists();
14 changes: 14 additions & 0 deletions tests/cases/fourslash/getJavaScriptSemanticDiagnostics23.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />

// @allowJs: true
// @Filename: a.js
//// function Person(age) {
//// if (age >= 18) {
//// this.canVote = true;
//// } else {
//// this.canVote = false;
//// }
//// }

verify.getSyntacticDiagnostics(`[]`);
verify.getSemanticDiagnostics(`[]`);
Loading