Merge pull request #5266 from Microsoft/javaScriptModules

JavaScript LS scaffolding + JS module inference
This commit is contained in:
Ryan Cavanaugh 2015-11-10 11:59:09 -08:00
commit ad61788113
24 changed files with 825 additions and 308 deletions

View File

@ -1,3 +1,4 @@
/// <reference path="utilities.ts"/>
/// <reference path="parser.ts"/>
/* @internal */
@ -6,8 +7,8 @@ namespace ts {
export const enum ModuleInstanceState {
NonInstantiated = 0,
Instantiated = 1,
ConstEnumOnly = 2
Instantiated = 1,
ConstEnumOnly = 2
}
const enum Reachability {
@ -208,6 +209,9 @@ namespace ts {
return "__export";
case SyntaxKind.ExportAssignment:
return (<ExportAssignment>node).isExportEquals ? "export=" : "default";
case SyntaxKind.BinaryExpression:
// Binary expression case is for JS module 'module.exports = expr'
return "export=";
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
return node.flags & NodeFlags.Default ? "default" : undefined;
@ -1069,7 +1073,7 @@ namespace ts {
return "__" + indexOf((<SignatureDeclaration>node.parent).parameters, node);
}
function bind(node: Node) {
function bind(node: Node): void {
if (!node) {
return;
}
@ -1145,9 +1149,18 @@ namespace ts {
function bindWorker(node: Node) {
switch (node.kind) {
/* Strict mode checks */
case SyntaxKind.Identifier:
return checkStrictModeIdentifier(<Identifier>node);
case SyntaxKind.BinaryExpression:
if (isInJavaScriptFile(node)) {
if (isExportsPropertyAssignment(node)) {
bindExportsPropertyAssignment(<BinaryExpression>node);
}
else if (isModuleExportsAssignment(node)) {
bindModuleExportsAssignment(<BinaryExpression>node);
}
}
return checkStrictModeBinaryExpression(<BinaryExpression>node);
case SyntaxKind.CatchClause:
return checkStrictModeCatchClause(<CatchClause>node);
@ -1213,6 +1226,14 @@ namespace ts {
checkStrictModeFunctionName(<FunctionExpression>node);
const bindingName = (<FunctionExpression>node).name ? (<FunctionExpression>node).name.text : "__function";
return bindAnonymousDeclaration(<FunctionExpression>node, SymbolFlags.Function, bindingName);
case SyntaxKind.CallExpression:
if (isInJavaScriptFile(node)) {
bindCallExpression(<CallExpression>node);
}
break;
// Members of classes, interfaces, and modules
case SyntaxKind.ClassExpression:
case SyntaxKind.ClassDeclaration:
return bindClassLikeDeclaration(<ClassLikeDeclaration>node);
@ -1224,6 +1245,8 @@ namespace ts {
return bindEnumDeclaration(<EnumDeclaration>node);
case SyntaxKind.ModuleDeclaration:
return bindModuleDeclaration(<ModuleDeclaration>node);
// Imports and exports
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.NamespaceImport:
case SyntaxKind.ImportSpecifier:
@ -1243,16 +1266,21 @@ namespace ts {
function bindSourceFileIfExternalModule() {
setExportContextFlag(file);
if (isExternalModule(file)) {
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"`);
bindSourceFileAsExternalModule();
}
}
function bindExportAssignment(node: ExportAssignment) {
function bindSourceFileAsExternalModule() {
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName) }"`);
}
function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
const boundExpression = node.kind === SyntaxKind.ExportAssignment ? (<ExportAssignment>node).expression : (<BinaryExpression>node).right;
if (!container.symbol || !container.symbol.exports) {
// Export assignment in some sort of block construct
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
}
else if (node.expression.kind === SyntaxKind.Identifier) {
else if (boundExpression.kind === SyntaxKind.Identifier) {
// 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);
}
@ -1279,6 +1307,34 @@ namespace ts {
}
}
function setCommonJsModuleIndicator(node: Node) {
if (!file.commonJsModuleIndicator) {
file.commonJsModuleIndicator = node;
bindSourceFileAsExternalModule();
}
}
function bindExportsPropertyAssignment(node: BinaryExpression) {
// When we create a property via 'exports.foo = bar', the 'exports.foo' property access
// expression is the declaration
setCommonJsModuleIndicator(node);
declareSymbol(file.symbol.exports, file.symbol, <PropertyAccessExpression>node.left, SymbolFlags.Property | SymbolFlags.Export, SymbolFlags.None);
}
function bindModuleExportsAssignment(node: BinaryExpression) {
// 'module.exports = expr' assignment
setCommonJsModuleIndicator(node);
bindExportAssignment(node);
}
function bindCallExpression(node: CallExpression) {
// We're only inspecting call expressions to detect CommonJS modules, so we can skip
// this check if we've already seen the module indicator
if (!file.commonJsModuleIndicator && isRequireCall(node)) {
setCommonJsModuleIndicator(node);
}
}
function bindClassLikeDeclaration(node: ClassLikeDeclaration) {
if (node.kind === SyntaxKind.ClassDeclaration) {
bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes);

View File

@ -364,7 +364,7 @@ namespace ts {
}
function isGlobalSourceFile(node: Node) {
return node.kind === SyntaxKind.SourceFile && !isExternalModule(<SourceFile>node);
return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(<SourceFile>node);
}
function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol {
@ -480,7 +480,7 @@ namespace ts {
}
switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalModule(<SourceFile>location)) break;
if (!isExternalOrCommonJsModule(<SourceFile>location)) break;
case SyntaxKind.ModuleDeclaration:
const moduleExports = getSymbolOfNode(location).exports;
if (location.kind === SyntaxKind.SourceFile ||
@ -990,11 +990,16 @@ namespace ts {
// Module names are escaped in our symbol table. However, string literal values aren't.
// Escape the name in the "require(...)" clause to ensure we find the right symbol.
const moduleName = escapeIdentifier(moduleReferenceLiteral.text);
let moduleName = escapeIdentifier(moduleReferenceLiteral.text);
if (moduleName === undefined) {
return;
}
if (moduleName.indexOf("!") >= 0) {
moduleName = moduleName.substr(0, moduleName.indexOf("!"));
}
const isRelative = isExternalModuleNameRelative(moduleName);
if (!isRelative) {
const symbol = getSymbol(globals, "\"" + moduleName + "\"", SymbolFlags.ValueModule);
@ -1205,7 +1210,7 @@ namespace ts {
}
switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalModule(<SourceFile>location)) {
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
break;
}
case SyntaxKind.ModuleDeclaration:
@ -1387,7 +1392,7 @@ namespace ts {
function hasExternalModuleSymbol(declaration: Node) {
return (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).name.kind === SyntaxKind.StringLiteral) ||
(declaration.kind === SyntaxKind.SourceFile && isExternalModule(<SourceFile>declaration));
(declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>declaration));
}
function hasVisibleDeclarations(symbol: Symbol): SymbolVisibilityResult {
@ -2061,7 +2066,7 @@ namespace ts {
}
}
else if (node.kind === SyntaxKind.SourceFile) {
return isExternalModule(<SourceFile>node) ? node : undefined;
return isExternalOrCommonJsModule(<SourceFile>node) ? node : undefined;
}
}
Debug.fail("getContainingModule cant reach here");
@ -2182,7 +2187,7 @@ namespace ts {
case SyntaxKind.SourceFile:
return true;
// Export assignements do not create name bindings outside the module
// Export assignments do not create name bindings outside the module
case SyntaxKind.ExportAssignment:
return false;
@ -2567,6 +2572,14 @@ namespace ts {
if (declaration.kind === SyntaxKind.ExportAssignment) {
return links.type = checkExpression((<ExportAssignment>declaration).expression);
}
// Handle module.exports = expr
if (declaration.kind === SyntaxKind.BinaryExpression) {
return links.type = checkExpression((<BinaryExpression>declaration).right);
}
// Handle exports.p = expr
if (declaration.kind === SyntaxKind.PropertyAccessExpression) {
return checkExpressionCached((<BinaryExpression>declaration.parent).right);
}
// Handle variable, parameter or property
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return unknownType;
@ -3841,6 +3854,18 @@ namespace ts {
return result;
}
function resolveExternalModuleTypeByLiteral(name: StringLiteral) {
const moduleSym = resolveExternalModuleName(name, name);
if (moduleSym) {
const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym);
if (resolvedModuleSymbol) {
return getTypeOfSymbol(resolvedModuleSymbol);
}
}
return anyType;
}
function getReturnTypeOfSignature(signature: Signature): Type {
if (!signature.resolvedReturnType) {
if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) {
@ -9426,6 +9451,12 @@ namespace ts {
return anyType;
}
}
// In JavaScript files, calls to any identifier 'require' are treated as external module imports
if (isInJavaScriptFile(node) && isRequireCall(node)) {
return resolveExternalModuleTypeByLiteral(<StringLiteral>node.arguments[0]);
}
return getReturnTypeOfSignature(signature);
}
@ -12067,7 +12098,7 @@ namespace ts {
// In case of variable declaration, node.parent is variable statement so look at the variable statement's parent
const parent = getDeclarationContainer(node);
if (parent.kind === SyntaxKind.SourceFile && isExternalModule(<SourceFile>parent)) {
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent)) {
// If the declaration happens to be in external module, report error that require and exports are reserved keywords
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
declarationNameToString(name), declarationNameToString(name));
@ -14150,7 +14181,7 @@ namespace ts {
forEach(node.statements, checkSourceElement);
checkFunctionAndClassExpressionBodies(node);
if (isExternalModule(node)) {
if (isExternalOrCommonJsModule(node)) {
checkExternalModuleExports(node);
}
@ -14253,7 +14284,7 @@ namespace ts {
switch (location.kind) {
case SyntaxKind.SourceFile:
if (!isExternalModule(<SourceFile>location)) {
if (!isExternalOrCommonJsModule(<SourceFile>location)) {
break;
}
case SyntaxKind.ModuleDeclaration:
@ -14999,16 +15030,16 @@ namespace ts {
// Initialize global symbol table
forEach(host.getSourceFiles(), file => {
if (!isExternalModule(file)) {
if (!isExternalOrCommonJsModule(file)) {
mergeSymbolTable(globals, file.locals);
}
});
// Initialize special symbols
getSymbolLinks(undefinedSymbol).type = undefinedType;
getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments");
getSymbolLinks(unknownSymbol).type = unknownType;
globals[undefinedSymbol.name] = undefinedSymbol;
// Initialize special types
globalArrayType = <GenericType>getGlobalType("Array", /*arity*/ 1);
globalObjectType = getGlobalType("Object");

View File

@ -739,12 +739,7 @@ namespace ts {
* List of supported extensions in order of file resolution precedence.
*/
export const supportedExtensions = [".ts", ".tsx", ".d.ts"];
/**
* List of extensions that will be used to look for external modules.
* This list is kept separate from supportedExtensions to for cases when we'll allow to include .js files in compilation,
* but still would like to load only TypeScript files as modules
*/
export const moduleFileExtensions = supportedExtensions;
export const supportedJsExtensions = supportedExtensions.concat(".js", ".jsx");
export function isSupportedSourceFileName(fileName: string) {
if (!fileName) { return false; }

View File

@ -1,5 +1,5 @@
/// <reference path="scanner.ts"/>
/// <reference path="utilities.ts"/>
/// <reference path="scanner.ts"/>
namespace ts {
/* @internal */ export let parseTime = 0;
@ -534,7 +534,8 @@ namespace ts {
let parseErrorBeforeNextFinishedNode = false;
export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile {
initializeState(fileName, _sourceText, languageVersion, _syntaxCursor);
const isJavaScriptFile = hasJavaScriptFileExtension(fileName) || _sourceText.lastIndexOf("// @language=javascript", 0) === 0;
initializeState(fileName, _sourceText, languageVersion, isJavaScriptFile, _syntaxCursor);
const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes);
@ -543,7 +544,7 @@ namespace ts {
return result;
}
function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor) {
function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, isJavaScriptFile: boolean, _syntaxCursor: IncrementalParser.SyntaxCursor) {
NodeConstructor = objectAllocator.getNodeConstructor();
SourceFileConstructor = objectAllocator.getSourceFileConstructor();
@ -556,14 +557,14 @@ namespace ts {
identifierCount = 0;
nodeCount = 0;
contextFlags = isJavaScript(fileName) ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None;
contextFlags = isJavaScriptFile ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None;
parseErrorBeforeNextFinishedNode = false;
// Initialize and prime the scanner before parsing the source elements.
scanner.setText(sourceText);
scanner.setOnError(scanError);
scanner.setScriptTarget(languageVersion);
scanner.setLanguageVariant(isTsx(fileName) ? LanguageVariant.JSX : LanguageVariant.Standard);
scanner.setLanguageVariant(allowsJsxExpressions(fileName) ? LanguageVariant.JSX : LanguageVariant.Standard);
}
function clearState() {
@ -582,6 +583,10 @@ namespace ts {
function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean): SourceFile {
sourceFile = createSourceFile(fileName, languageVersion);
if (contextFlags & ParserContextFlags.JavaScriptFile) {
sourceFile.parserContextFlags = ParserContextFlags.JavaScriptFile;
}
// Prime the scanner.
token = nextToken();
processReferenceComments(sourceFile);
@ -604,7 +609,7 @@ namespace ts {
// If this is a javascript file, proactively see if we can get JSDoc comments for
// relevant nodes in the file. We'll use these to provide typing informaion if they're
// available.
if (isJavaScript(fileName)) {
if (isSourceFileJavaScript(sourceFile)) {
addJSDocComments();
}
@ -677,7 +682,7 @@ namespace ts {
sourceFile.languageVersion = languageVersion;
sourceFile.fileName = normalizePath(fileName);
sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0;
sourceFile.languageVariant = isTsx(sourceFile.fileName) ? LanguageVariant.JSX : LanguageVariant.Standard;
sourceFile.languageVariant = allowsJsxExpressions(sourceFile.fileName) ? LanguageVariant.JSX : LanguageVariant.Standard;
return sourceFile;
}
@ -5519,7 +5524,7 @@ namespace ts {
}
export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) {
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined);
initializeState("file.js", content, ScriptTarget.Latest, /*isJavaScriptFile*/ true, /*_syntaxCursor:*/ undefined);
const jsDocTypeExpression = parseJSDocTypeExpression(start, length);
const diagnostics = parseDiagnostics;
clearState();
@ -5840,7 +5845,7 @@ namespace ts {
}
export function parseIsolatedJSDocComment(content: string, start: number, length: number) {
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined);
initializeState("file.js", content, ScriptTarget.Latest, /*isJavaScriptFile*/ true, /*_syntaxCursor:*/ undefined);
const jsDocComment = parseJSDocComment(/*parent:*/ undefined, start, length);
const diagnostics = parseDiagnostics;
clearState();

View File

@ -53,13 +53,13 @@ namespace ts {
if (getRootLength(moduleName) !== 0 || nameStartsWithDotSlashOrDotDotSlash(moduleName)) {
const failedLookupLocations: string[] = [];
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
let resolvedFileName = loadNodeModuleFromFile(candidate, failedLookupLocations, host);
let resolvedFileName = loadNodeModuleFromFile(supportedJsExtensions, candidate, failedLookupLocations, host);
if (resolvedFileName) {
return { resolvedModule: { resolvedFileName }, failedLookupLocations };
}
resolvedFileName = loadNodeModuleFromDirectory(candidate, failedLookupLocations, host);
resolvedFileName = loadNodeModuleFromDirectory(supportedJsExtensions, candidate, failedLookupLocations, host);
return resolvedFileName
? { resolvedModule: { resolvedFileName }, failedLookupLocations }
: { resolvedModule: undefined, failedLookupLocations };
@ -69,8 +69,8 @@ namespace ts {
}
}
function loadNodeModuleFromFile(candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string {
return forEach(moduleFileExtensions, tryLoad);
function loadNodeModuleFromFile(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string {
return forEach(extensions, tryLoad);
function tryLoad(ext: string): string {
const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext;
@ -84,7 +84,7 @@ namespace ts {
}
}
function loadNodeModuleFromDirectory(candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string {
function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], host: ModuleResolutionHost): string {
const packageJsonPath = combinePaths(candidate, "package.json");
if (host.fileExists(packageJsonPath)) {
@ -100,7 +100,7 @@ namespace ts {
}
if (jsonContent.typings) {
const result = loadNodeModuleFromFile(normalizePath(combinePaths(candidate, jsonContent.typings)), failedLookupLocation, host);
const result = loadNodeModuleFromFile(extensions, normalizePath(combinePaths(candidate, jsonContent.typings)), failedLookupLocation, host);
if (result) {
return result;
}
@ -111,7 +111,7 @@ namespace ts {
failedLookupLocation.push(packageJsonPath);
}
return loadNodeModuleFromFile(combinePaths(candidate, "index"), failedLookupLocation, host);
return loadNodeModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocation, host);
}
function loadModuleFromNodeModules(moduleName: string, directory: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
@ -122,12 +122,12 @@ namespace ts {
if (baseName !== "node_modules") {
const nodeModulesFolder = combinePaths(directory, "node_modules");
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
let result = loadNodeModuleFromFile(candidate, failedLookupLocations, host);
let result = loadNodeModuleFromFile(supportedExtensions, candidate, failedLookupLocations, host);
if (result) {
return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations };
}
result = loadNodeModuleFromDirectory(candidate, failedLookupLocations, host);
result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, host);
if (result) {
return { resolvedModule: { resolvedFileName: result, isExternalLibraryImport: true }, failedLookupLocations };
}
@ -162,9 +162,10 @@ namespace ts {
const failedLookupLocations: string[] = [];
let referencedSourceFile: string;
const extensions = compilerOptions.allowNonTsExtensions ? supportedJsExtensions : supportedExtensions;
while (true) {
searchName = normalizePath(combinePaths(searchPath, moduleName));
referencedSourceFile = forEach(supportedExtensions, extension => {
referencedSourceFile = forEach(extensions, extension => {
if (extension === ".tsx" && !compilerOptions.jsx) {
// resolve .tsx files only if jsx support is enabled
// 'logical not' handles both undefined and None cases
@ -688,45 +689,60 @@ namespace ts {
return;
}
const isJavaScriptFile = isSourceFileJavaScript(file);
let imports: LiteralExpression[];
for (const node of file.statements) {
collect(node, /* allowRelativeModuleNames */ true);
collect(node, /* allowRelativeModuleNames */ true, /* collectOnlyRequireCalls */ false);
}
file.imports = imports || emptyArray;
function collect(node: Node, allowRelativeModuleNames: boolean): void {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ExportDeclaration:
let moduleNameExpr = getExternalModuleName(node);
if (!moduleNameExpr || moduleNameExpr.kind !== SyntaxKind.StringLiteral) {
break;
}
if (!(<LiteralExpression>moduleNameExpr).text) {
break;
}
return;
if (allowRelativeModuleNames || !isExternalModuleNameRelative((<LiteralExpression>moduleNameExpr).text)) {
(imports || (imports = [])).push(<LiteralExpression>moduleNameExpr);
}
break;
case SyntaxKind.ModuleDeclaration:
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) {
// TypeScript 1.0 spec (April 2014): 12.1.6
// An AmbientExternalModuleDeclaration declares an external module.
// This type of declaration is permitted only in the global module.
// The StringLiteral must specify a top - level external module name.
// Relative external module names are not permitted
forEachChild((<ModuleDeclaration>node).body, node => {
function collect(node: Node, allowRelativeModuleNames: boolean, collectOnlyRequireCalls: boolean): void {
if (!collectOnlyRequireCalls) {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ExportDeclaration:
let moduleNameExpr = getExternalModuleName(node);
if (!moduleNameExpr || moduleNameExpr.kind !== SyntaxKind.StringLiteral) {
break;
}
if (!(<LiteralExpression>moduleNameExpr).text) {
break;
}
if (allowRelativeModuleNames || !isExternalModuleNameRelative((<LiteralExpression>moduleNameExpr).text)) {
(imports || (imports = [])).push(<LiteralExpression>moduleNameExpr);
}
break;
case SyntaxKind.ModuleDeclaration:
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) {
// TypeScript 1.0 spec (April 2014): 12.1.6
// An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules
// only through top - level external module names. Relative external module names are not permitted.
collect(node, /* allowRelativeModuleNames */ false);
});
}
break;
// An AmbientExternalModuleDeclaration declares an external module.
// This type of declaration is permitted only in the global module.
// The StringLiteral must specify a top - level external module name.
// Relative external module names are not permitted
forEachChild((<ModuleDeclaration>node).body, node => {
// TypeScript 1.0 spec (April 2014): 12.1.6
// An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules
// only through top - level external module names. Relative external module names are not permitted.
collect(node, /* allowRelativeModuleNames */ false, collectOnlyRequireCalls);
});
}
break;
}
}
if (isJavaScriptFile) {
if (isRequireCall(node)) {
(imports || (imports = [])).push(<StringLiteral>(<CallExpression>node).arguments[0]);
}
else {
forEachChild(node, node => collect(node, allowRelativeModuleNames, /* collectOnlyRequireCalls */ true));
}
}
}
}

View File

@ -773,7 +773,8 @@ namespace ts {
expression?: Expression;
}
export interface BinaryExpression extends Expression {
// Binary expressions can be declarations if they are 'exports.foo = bar' expressions in JS files
export interface BinaryExpression extends Expression, Declaration {
left: Expression;
operatorToken: Node;
right: Expression;
@ -834,7 +835,7 @@ namespace ts {
properties: NodeArray<ObjectLiteralElement>;
}
export interface PropertyAccessExpression extends MemberExpression {
export interface PropertyAccessExpression extends MemberExpression, Declaration {
expression: LeftHandSideExpression;
dotToken: Node;
name: Identifier;
@ -1284,6 +1285,8 @@ namespace ts {
// The first node that causes this file to be an external module
/* @internal */ externalModuleIndicator: Node;
// The first node that causes this file to be a CommonJS module
/* @internal */ commonJsModuleIndicator: Node;
/* @internal */ identifiers: Map<string>;
/* @internal */ nodeCount: number;

View File

@ -1,4 +1,3 @@
/// <reference path="binder.ts" />
/// <reference path="sys.ts" />
/* @internal */
@ -362,6 +361,10 @@ namespace ts {
return file.externalModuleIndicator !== undefined;
}
export function isExternalOrCommonJsModule(file: SourceFile): boolean {
return (file.externalModuleIndicator || file.commonJsModuleIndicator) !== undefined;
}
export function isDeclarationFile(file: SourceFile): boolean {
return (file.flags & NodeFlags.DeclarationFile) !== 0;
}
@ -1056,6 +1059,57 @@ namespace ts {
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
}
export function isSourceFileJavaScript(file: SourceFile): boolean {
return isInJavaScriptFile(file);
}
export function isInJavaScriptFile(node: Node): boolean {
return node && !!(node.parserContextFlags & ParserContextFlags.JavaScriptFile);
}
/**
* Returns true if the node is a CallExpression to the identifier 'require' with
* exactly one string literal argument.
* This function does not test if the node is in a JavaScript file or not.
*/
export function isRequireCall(expression: Node): expression is CallExpression {
// of the form 'require("name")'
return expression.kind === SyntaxKind.CallExpression &&
(<CallExpression>expression).expression.kind === SyntaxKind.Identifier &&
(<Identifier>(<CallExpression>expression).expression).text === "require" &&
(<CallExpression>expression).arguments.length === 1 &&
(<CallExpression>expression).arguments[0].kind === SyntaxKind.StringLiteral;
}
/**
* Returns true if the node is an assignment to a property on the identifier 'exports'.
* This function does not test if the node is in a JavaScript file or not.
*/
export function isExportsPropertyAssignment(expression: Node): boolean {
// of the form 'exports.name = expr' where 'name' and 'expr' are arbitrary
return isInJavaScriptFile(expression) &&
(expression.kind === SyntaxKind.BinaryExpression) &&
((<BinaryExpression>expression).operatorToken.kind === SyntaxKind.EqualsToken) &&
((<BinaryExpression>expression).left.kind === SyntaxKind.PropertyAccessExpression) &&
((<PropertyAccessExpression>(<BinaryExpression>expression).left).expression.kind === SyntaxKind.Identifier) &&
((<Identifier>((<PropertyAccessExpression>(<BinaryExpression>expression).left).expression)).text === "exports");
}
/**
* Returns true if the node is an assignment to the property access expression 'module.exports'.
* This function does not test if the node is in a JavaScript file or not.
*/
export function isModuleExportsAssignment(expression: Node): boolean {
// of the form 'module.exports = expr' where 'expr' is arbitrary
return isInJavaScriptFile(expression) &&
(expression.kind === SyntaxKind.BinaryExpression) &&
((<BinaryExpression>expression).operatorToken.kind === SyntaxKind.EqualsToken) &&
((<BinaryExpression>expression).left.kind === SyntaxKind.PropertyAccessExpression) &&
((<PropertyAccessExpression>(<BinaryExpression>expression).left).expression.kind === SyntaxKind.Identifier) &&
((<Identifier>((<PropertyAccessExpression>(<BinaryExpression>expression).left).expression)).text === "module") &&
((<PropertyAccessExpression>(<BinaryExpression>expression).left).name.text === "exports");
}
export function getExternalModuleName(node: Node): Expression {
if (node.kind === SyntaxKind.ImportDeclaration) {
return (<ImportDeclaration>node).moduleSpecifier;
@ -2210,12 +2264,12 @@ namespace ts {
return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
}
export function isJavaScript(fileName: string) {
return fileExtensionIs(fileName, ".js");
export function hasJavaScriptFileExtension(fileName: string) {
return fileExtensionIs(fileName, ".js") || fileExtensionIs(fileName, ".jsx");
}
export function isTsx(fileName: string) {
return fileExtensionIs(fileName, ".tsx");
export function allowsJsxExpressions(fileName: string) {
return fileExtensionIs(fileName, ".tsx") || fileExtensionIs(fileName, ".jsx");
}
/**

View File

@ -223,10 +223,22 @@ namespace FourSlash {
// Add input file which has matched file name with the given reference-file path.
// This is necessary when resolveReference flag is specified
private addMatchedInputFile(referenceFilePath: string) {
const inputFile = this.inputFiles[referenceFilePath];
if (inputFile && !Harness.isLibraryFile(referenceFilePath)) {
this.languageServiceAdapterHost.addScript(referenceFilePath, inputFile);
private addMatchedInputFile(referenceFilePath: string, extensions: string[]) {
const inputFiles = this.inputFiles;
const languageServiceAdapterHost = this.languageServiceAdapterHost;
if (!extensions) {
tryAdd(referenceFilePath);
}
else {
tryAdd(referenceFilePath) || ts.forEach(extensions, ext => tryAdd(referenceFilePath + ext));
}
function tryAdd(path: string) {
const inputFile = inputFiles[path];
if (inputFile && !Harness.isLibraryFile(path)) {
languageServiceAdapterHost.addScript(path, inputFile);
return true;
}
}
}
@ -280,15 +292,15 @@ namespace FourSlash {
ts.forEach(referencedFiles, referenceFile => {
// Fourslash insert tests/cases/fourslash into inputFile.unitName so we will properly append the same base directory to refFile path
const referenceFilePath = this.basePath + "/" + referenceFile.fileName;
this.addMatchedInputFile(referenceFilePath);
this.addMatchedInputFile(referenceFilePath, /* extensions */ undefined);
});
// Add import files into language-service host
ts.forEach(importedFiles, importedFile => {
// Fourslash insert tests/cases/fourslash into inputFile.unitName and import statement doesn't require ".ts"
// so convert them before making appropriate comparison
const importedFilePath = this.basePath + "/" + importedFile.fileName + ".ts";
this.addMatchedInputFile(importedFilePath);
const importedFilePath = this.basePath + "/" + importedFile.fileName;
this.addMatchedInputFile(importedFilePath, compilationOptions.allowNonTsExtensions ? ts.supportedJsExtensions : ts.supportedExtensions);
});
// Check if no-default-lib flag is false and if so add default library
@ -2257,15 +2269,15 @@ namespace FourSlash {
const details = this.getCompletionEntryDetails(item.name);
if (documentation !== undefined) {
assert.equal(ts.displayPartsToString(details.documentation), documentation, assertionMessage("completion item documentation"));
assert.equal(ts.displayPartsToString(details.documentation), documentation, assertionMessage("completion item documentation for " + name));
}
if (text !== undefined) {
assert.equal(ts.displayPartsToString(details.displayParts), text, assertionMessage("completion item detail text"));
assert.equal(ts.displayPartsToString(details.displayParts), text, assertionMessage("completion item detail text for " + name));
}
}
if (kind !== undefined) {
assert.equal(item.kind, kind, assertionMessage("completion item kind"));
assert.equal(item.kind, kind, assertionMessage("completion item kind for " + name));
}
return;

View File

@ -197,7 +197,7 @@ namespace Harness.LanguageService {
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
getClassifier(): ts.Classifier { return ts.createClassifier(); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJavaScriptFileExtension(fileName)); }
}
/// Shim adapter

View File

@ -371,6 +371,10 @@ namespace ts.server {
openRefCount = 0;
constructor(public projectService: ProjectService, public projectOptions?: ProjectOptions) {
if (projectOptions && projectOptions.files) {
// If files are listed explicitly, allow all extensions
projectOptions.compilerOptions.allowNonTsExtensions = true;
}
this.compilerService = new CompilerService(this, projectOptions && projectOptions.compilerOptions);
}
@ -461,6 +465,7 @@ namespace ts.server {
setProjectOptions(projectOptions: ProjectOptions) {
this.projectOptions = projectOptions;
if (projectOptions.compilerOptions) {
projectOptions.compilerOptions.allowNonTsExtensions = true;
this.compilerService.setCompilerOptions(projectOptions.compilerOptions);
}
}
@ -1316,7 +1321,9 @@ namespace ts.server {
this.setCompilerOptions(opt);
}
else {
this.setCompilerOptions(ts.getDefaultCompilerOptions());
const defaultOpts = ts.getDefaultCompilerOptions();
defaultOpts.allowNonTsExtensions = true;
this.setCompilerOptions(defaultOpts);
}
this.languageService = ts.createLanguageService(this.host, this.documentRegistry);
this.classifier = ts.createClassifier();

View File

@ -801,6 +801,7 @@ namespace ts {
public isDefaultLib: boolean;
public hasNoDefaultLib: boolean;
public externalModuleIndicator: Node; // The first node that causes this file to be an external module
public commonJsModuleIndicator: Node; // The first node that causes this file to be a CommonJS module
public nodeCount: number;
public identifierCount: number;
public symbolCount: number;
@ -2124,7 +2125,7 @@ namespace ts {
};
}
export function preProcessFile(sourceText: string, readImportFiles = true): PreProcessedFileInfo {
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
let referencedFiles: FileReference[] = [];
let importedFiles: FileReference[] = [];
let ambientExternalModules: string[];
@ -2162,9 +2163,225 @@ namespace ts {
});
}
function processImport(): void {
/**
* Returns true if at least one token was consumed from the stream
*/
function tryConsumeDeclare(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.DeclareKeyword) {
// declare module "mod"
token = scanner.scan();
if (token === SyntaxKind.ModuleKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
recordAmbientExternalModule();
}
}
return true;
}
return false;
}
/**
* Returns true if at least one token was consumed from the stream
*/
function tryConsumeImport(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.ImportKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import "mod";
recordModuleName();
return true;
}
else {
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import d from "mod";
recordModuleName();
return true;
}
}
else if (token === SyntaxKind.EqualsToken) {
if (tryConsumeRequireCall(/* skipCurrentToken */ true)) {
return true;
}
}
else if (token === SyntaxKind.CommaToken) {
// consume comma and keep going
token = scanner.scan();
}
else {
// unknown syntax
return true;
}
}
if (token === SyntaxKind.OpenBraceToken) {
token = scanner.scan();
// consume "{ a as B, c, d as D}" clauses
// make sure that it stops on EOF
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
token = scanner.scan();
}
if (token === SyntaxKind.CloseBraceToken) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import {a as A} from "mod";
// import d, {a, b as B} from "mod"
recordModuleName();
}
}
}
}
else if (token === SyntaxKind.AsteriskToken) {
token = scanner.scan();
if (token === SyntaxKind.AsKeyword) {
token = scanner.scan();
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import * as NS from "mod"
// import d, * as NS from "mod"
recordModuleName();
}
}
}
}
}
}
return true;
}
return false;
}
function tryConsumeExport(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.ExportKeyword) {
token = scanner.scan();
if (token === SyntaxKind.OpenBraceToken) {
token = scanner.scan();
// consume "{ a as B, c, d as D}" clauses
// make sure it stops on EOF
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
token = scanner.scan();
}
if (token === SyntaxKind.CloseBraceToken) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// export {a as A} from "mod";
// export {a, b as B} from "mod"
recordModuleName();
}
}
}
}
else if (token === SyntaxKind.AsteriskToken) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// export * from "mod"
recordModuleName();
}
}
}
else if (token === SyntaxKind.ImportKeyword) {
token = scanner.scan();
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = scanner.scan();
if (token === SyntaxKind.EqualsToken) {
if (tryConsumeRequireCall(/* skipCurrentToken */ true)) {
return true;
}
}
}
}
return true;
}
return false;
}
function tryConsumeRequireCall(skipCurrentToken: boolean): boolean {
let token = skipCurrentToken ? scanner.scan() : scanner.getToken();
if (token === SyntaxKind.RequireKeyword) {
token = scanner.scan();
if (token === SyntaxKind.OpenParenToken) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// require("mod");
recordModuleName();
}
}
return true;
}
return false;
}
function tryConsumeDefine(): boolean {
let token = scanner.getToken();
if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") {
token = scanner.scan();
if (token !== SyntaxKind.OpenParenToken) {
return true;
}
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// looks like define ("modname", ... - skip string literal and comma
token = scanner.scan();
if (token === SyntaxKind.CommaToken) {
token = scanner.scan();
}
else {
// unexpected token
return true;
}
}
// should be start of dependency list
if (token !== SyntaxKind.OpenBracketToken) {
return true;
}
// skip open bracket
token = scanner.scan();
let i = 0;
// scan until ']' or EOF
while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) {
// record string literals as module names
if (token === SyntaxKind.StringLiteral) {
recordModuleName();
i++;
}
token = scanner.scan();
}
return true;
}
return false;
}
function processImports(): void {
scanner.setText(sourceText);
let token = scanner.scan();
scanner.scan();
// Look for:
// import "mod";
// import d from "mod"
@ -2176,157 +2393,30 @@ namespace ts {
// export * from "mod"
// export {a as b} from "mod"
// export import i = require("mod")
// (for JavaScript files) require("mod")
while (token !== SyntaxKind.EndOfFileToken) {
if (token === SyntaxKind.DeclareKeyword) {
// declare module "mod"
token = scanner.scan();
if (token === SyntaxKind.ModuleKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
recordAmbientExternalModule();
continue;
}
}
while (true) {
if (scanner.getToken() === SyntaxKind.EndOfFileToken) {
break;
}
else if (token === SyntaxKind.ImportKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import "mod";
recordModuleName();
continue;
}
else {
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import d from "mod";
recordModuleName();
continue
}
}
else if (token === SyntaxKind.EqualsToken) {
token = scanner.scan();
if (token === SyntaxKind.RequireKeyword) {
token = scanner.scan();
if (token === SyntaxKind.OpenParenToken) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import i = require("mod");
recordModuleName();
continue;
}
}
}
}
else if (token === SyntaxKind.CommaToken) {
// consume comma and keep going
token = scanner.scan();
}
else {
// unknown syntax
continue;
}
}
if (token === SyntaxKind.OpenBraceToken) {
token = scanner.scan();
// consume "{ a as B, c, d as D}" clauses
while (token !== SyntaxKind.CloseBraceToken) {
token = scanner.scan();
}
if (token === SyntaxKind.CloseBraceToken) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import {a as A} from "mod";
// import d, {a, b as B} from "mod"
recordModuleName();
}
}
}
}
else if (token === SyntaxKind.AsteriskToken) {
token = scanner.scan();
if (token === SyntaxKind.AsKeyword) {
token = scanner.scan();
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// import * as NS from "mod"
// import d, * as NS from "mod"
recordModuleName();
}
}
}
}
}
}
// check if at least one of alternative have moved scanner forward
if (tryConsumeDeclare() ||
tryConsumeImport() ||
tryConsumeExport() ||
(detectJavaScriptImports && (tryConsumeRequireCall(/* skipCurrentToken */ false) || tryConsumeDefine()))) {
continue;
}
else if (token === SyntaxKind.ExportKeyword) {
token = scanner.scan();
if (token === SyntaxKind.OpenBraceToken) {
token = scanner.scan();
// consume "{ a as B, c, d as D}" clauses
while (token !== SyntaxKind.CloseBraceToken) {
token = scanner.scan();
}
if (token === SyntaxKind.CloseBraceToken) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// export {a as A} from "mod";
// export {a, b as B} from "mod"
recordModuleName();
}
}
}
}
else if (token === SyntaxKind.AsteriskToken) {
token = scanner.scan();
if (token === SyntaxKind.FromKeyword) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// export * from "mod"
recordModuleName();
}
}
}
else if (token === SyntaxKind.ImportKeyword) {
token = scanner.scan();
if (token === SyntaxKind.Identifier || isKeyword(token)) {
token = scanner.scan();
if (token === SyntaxKind.EqualsToken) {
token = scanner.scan();
if (token === SyntaxKind.RequireKeyword) {
token = scanner.scan();
if (token === SyntaxKind.OpenParenToken) {
token = scanner.scan();
if (token === SyntaxKind.StringLiteral) {
// export import i = require("mod");
recordModuleName();
}
}
}
}
}
}
else {
scanner.scan();
}
token = scanner.scan();
}
scanner.setText(undefined);
}
if (readImportFiles) {
processImport();
processImports();
}
processTripleSlashDirectives();
return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules };
@ -2815,7 +2905,7 @@ namespace ts {
// For JavaScript files, we don't want to report the normal typescript semantic errors.
// Instead, we just report errors for using TypeScript-only constructs from within a
// JavaScript file.
if (isJavaScript(fileName)) {
if (isSourceFileJavaScript(targetSourceFile)) {
return getJavaScriptSemanticDiagnostics(targetSourceFile);
}
@ -3054,7 +3144,7 @@ namespace ts {
let typeChecker = program.getTypeChecker();
let syntacticStart = new Date().getTime();
let sourceFile = getValidSourceFile(fileName);
let isJavaScriptFile = isJavaScript(fileName);
let isJavaScriptFile = isSourceFileJavaScript(sourceFile);
let isJsDocTagName = false;
@ -3875,22 +3965,25 @@ namespace ts {
let { symbols, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot, isJsDocTagName } = completionData;
let entries: CompletionEntry[];
if (isJsDocTagName) {
// If the current position is a jsDoc tag name, only tag names should be provided for completion
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() };
}
if (isRightOfDot && isJavaScript(fileName)) {
entries = getCompletionEntriesFromSymbols(symbols);
addRange(entries, getJavaScriptCompletionEntries());
let sourceFile = getValidSourceFile(fileName);
let entries: CompletionEntry[] = [];
if (isRightOfDot && isSourceFileJavaScript(sourceFile)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries);
addRange(entries, getJavaScriptCompletionEntries(sourceFile, uniqueNames));
}
else {
if (!symbols || symbols.length === 0) {
return undefined;
}
entries = getCompletionEntriesFromSymbols(symbols);
getCompletionEntriesFromSymbols(symbols, entries);
}
// Add keywords if this is not a member completion list
@ -3900,26 +3993,23 @@ namespace ts {
return { isMemberCompletion, isNewIdentifierLocation, entries };
function getJavaScriptCompletionEntries(): CompletionEntry[] {
function getJavaScriptCompletionEntries(sourceFile: SourceFile, uniqueNames: Map<string>): CompletionEntry[] {
let entries: CompletionEntry[] = [];
let allNames: Map<string> = {};
let target = program.getCompilerOptions().target;
for (let sourceFile of program.getSourceFiles()) {
let nameTable = getNameTable(sourceFile);
for (let name in nameTable) {
if (!allNames[name]) {
allNames[name] = name;
let displayName = getCompletionEntryDisplayName(name, target, /*performCharacterChecks:*/ true);
if (displayName) {
let entry = {
name: displayName,
kind: ScriptElementKind.warning,
kindModifiers: "",
sortText: "1"
};
entries.push(entry);
}
let nameTable = getNameTable(sourceFile);
for (let name in nameTable) {
if (!uniqueNames[name]) {
uniqueNames[name] = name;
let displayName = getCompletionEntryDisplayName(name, target, /*performCharacterChecks:*/ true);
if (displayName) {
let entry = {
name: displayName,
kind: ScriptElementKind.warning,
kindModifiers: "",
sortText: "1"
};
entries.push(entry);
}
}
}
@ -3963,26 +4053,24 @@ namespace ts {
};
}
function getCompletionEntriesFromSymbols(symbols: Symbol[]): CompletionEntry[] {
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map<string> {
let start = new Date().getTime();
let entries: CompletionEntry[] = [];
let uniqueNames: Map<string> = {};
if (symbols) {
let nameToSymbol: Map<Symbol> = {};
for (let symbol of symbols) {
let entry = createCompletionEntry(symbol, location);
if (entry) {
let id = escapeIdentifier(entry.name);
if (!lookUp(nameToSymbol, id)) {
if (!lookUp(uniqueNames, id)) {
entries.push(entry);
nameToSymbol[id] = symbol;
uniqueNames[id] = id;
}
}
}
}
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start));
return entries;
return uniqueNames;
}
}

View File

@ -956,7 +956,8 @@ namespace ts {
return this.forwardJSONCall(
"getPreProcessedFileInfo('" + fileName + "')",
() => {
var result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()));
// for now treat files as JavaScript
var result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
var convertResult = {
referencedFiles: <IFileReference[]>[],
importedFiles: <IFileReference[]>[],

View File

@ -204,7 +204,7 @@ namespace ts.SignatureHelp {
if (!candidates.length) {
// We didn't have any sig help items produced by the TS compiler. If this is a JS
// file, then see if we can figure out anything better.
if (isJavaScript(sourceFile.fileName)) {
if (isSourceFileJavaScript(sourceFile)) {
return createJavaScriptSignatureHelpItems(argumentInfo);
}

View File

@ -1,19 +0,0 @@
/// <reference path="fourslash.ts" />
// @allowNonTsExtensions: true
// @Filename: a.js
//// /**
//// * @type {number}
//// * @type {string}
//// */
//// var v;
verify.getSyntacticDiagnostics(`[
{
"message": "\'type\' tag already specified.",
"start": 26,
"length": 4,
"category": "error",
"code": 1223
}
]`);

View File

@ -0,0 +1,57 @@
///<reference path="fourslash.ts" />
// Invocations of 'require' stop top-level variables from becoming global
// @allowNonTsExtensions: true
// @Filename: mod1.js
//// var x = require('fs');
//// /*1*/
// @Filename: mod2.js
//// var y;
//// if(true) {
//// y = require('fs');
//// }
//// /*2*/
// @Filename: glob1.js
//// var a = require;
//// /*3*/
// @Filename: glob2.js
//// var b = '';
//// /*4*/
// @Filename: consumer.js
//// /*5*/
goTo.marker('1');
verify.completionListContains('x');
verify.not.completionListContains('y');
verify.completionListContains('a');
verify.completionListContains('b');
goTo.marker('2');
verify.not.completionListContains('x');
verify.completionListContains('y');
verify.completionListContains('a');
verify.completionListContains('b');
goTo.marker('3');
verify.not.completionListContains('x');
verify.not.completionListContains('y');
verify.completionListContains('a');
verify.completionListContains('b');
goTo.marker('4');
verify.not.completionListContains('x');
verify.not.completionListContains('y');
verify.completionListContains('a');
verify.completionListContains('b');
goTo.marker('5');
verify.not.completionListContains('x');
verify.not.completionListContains('y');
verify.completionListContains('a');
verify.completionListContains('b');

View File

@ -0,0 +1,26 @@
///<reference path="fourslash.ts" />
// Assignments to 'module.exports' create an external module
// @allowNonTsExtensions: true
// @Filename: myMod.js
//// if (true) {
//// module.exports = { a: 10 };
//// }
//// var invisible = true;
// @Filename: isGlobal.js
//// var y = 10;
// @Filename: consumer.js
//// var x = require('myMod');
//// /**/;
goTo.file('consumer.js');
goTo.marker();
verify.completionListContains('y');
verify.not.completionListContains('invisible');
edit.insert('x.');
verify.completionListContains('a');

View File

@ -0,0 +1,28 @@
///<reference path="fourslash.ts" />
// Assignments to 'exports.p' stop global variables from being visible in other files
// @allowNonTsExtensions: true
// @Filename: myMod.js
//// if (true) {
//// exports.b = true;
//// } else {
//// exports.n = 3;
//// }
//// function fn() {
//// exports.s = 'foo';
//// }
//// var invisible = true;
// @Filename: isGlobal.js
//// var y = 10;
// @Filename: consumer.js
//// var x = require('myMod');
//// /**/;
goTo.file('consumer.js');
goTo.marker();
verify.completionListContains('y');
verify.not.completionListContains('invisible');

View File

@ -0,0 +1,27 @@
///<reference path="fourslash.ts" />
// Assignments to 'exports.p' define a property 'p' even if they're not at top-level
// @allowNonTsExtensions: true
// @Filename: myMod.js
//// if (true) {
//// exports.b = true;
//// } else {
//// exports.n = 3;
//// }
//// function fn() {
//// exports.s = 'foo';
//// }
// @Filename: consumer.js
//// var x = require('myMod');
//// x/**/;
goTo.file('consumer.js');
goTo.marker();
edit.insert('.');
verify.completionListContains("n", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
verify.completionListContains("s", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
verify.completionListContains("b", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
edit.insert('n.');
verify.completionListContains("toFixed", /*displayText:*/ undefined, /*documentation*/ undefined, "method");

View File

@ -0,0 +1,22 @@
///<reference path="fourslash.ts" />
// Assignments to 'exports.p' define a property 'p'
// @allowNonTsExtensions: true
// @Filename: myMod.js
//// exports.n = 3;
//// exports.s = 'foo';
//// exports.b = true;
// @Filename: consumer.js
//// var x = require('myMod');
//// x/**/;
goTo.file('consumer.js');
goTo.marker();
edit.insert('.');
verify.completionListContains("n", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
verify.completionListContains("s", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
verify.completionListContains("b", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
edit.insert('n.');
verify.completionListContains("toFixed", /*displayText:*/ undefined, /*documentation*/ undefined, "method");

View File

@ -0,0 +1,18 @@
///<reference path="fourslash.ts" />
// @allowNonTsExtensions: true
// @Filename: myMod.js
//// module.exports = { n: 3, s: 'foo', b: true };
// @Filename: consumer.js
//// var x = require('./myMod');
//// x/**/;
goTo.file('consumer.js');
goTo.marker();
edit.insert('.');
verify.completionListContains("n", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
verify.completionListContains("s", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
verify.completionListContains("b", /*displayText:*/ undefined, /*documentation*/ undefined, "property");
edit.insert('n.');
verify.completionListContains("toFixed", /*displayText:*/ undefined, /*documentation*/ undefined, "method");

View File

@ -0,0 +1,14 @@
///<reference path="fourslash.ts" />
// CommonJS modules should not pollute the global namespace
// @allowNonTsExtensions: true
// @Filename: myMod.js
//// var x = require('fs');
// @Filename: other.js
//// /**/;
goTo.file('other.js');
verify.not.completionListContains('x');

View File

@ -95,8 +95,8 @@ module ts {
let resolution = nodeModuleNameResolver(moduleName, containingFile.name, createModuleResolutionHost(containingFile, packageJson, moduleFile));
assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name);
assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false);
// expect three failed lookup location - attempt to load module as file with all supported extensions
assert.equal(resolution.failedLookupLocations.length, 3);
// expect five failed lookup location - attempt to load module as file with all supported extensions
assert.equal(resolution.failedLookupLocations.length, 5);
}
it("module name as directory - load from typings", () => {
@ -117,6 +117,8 @@ module ts {
"/a/b/foo.ts",
"/a/b/foo.tsx",
"/a/b/foo.d.ts",
"/a/b/foo.js",
"/a/b/foo.jsx",
"/a/b/foo/index.ts",
"/a/b/foo/index.tsx",
]);
@ -143,7 +145,7 @@ module ts {
"/a/b/c/node_modules/foo/package.json",
"/a/b/c/node_modules/foo/index.ts",
"/a/b/c/node_modules/foo/index.tsx",
"/a/b/c/node_modules/foo/index.d.ts"
"/a/b/c/node_modules/foo/index.d.ts",
])
});

View File

@ -2,8 +2,8 @@
/// <reference path="..\..\..\..\src\harness\harnessLanguageService.ts" />
describe('PreProcessFile:', function () {
function test(sourceText: string, readImportFile: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
var resultPreProcess = ts.preProcessFile(sourceText, readImportFile);
function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
var resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);
var resultIsLibFile = resultPreProcess.isLibFile;
var resultImportedFiles = resultPreProcess.importedFiles;
@ -45,7 +45,9 @@ describe('PreProcessFile:', function () {
}
describe("Test preProcessFiles,", function () {
it("Correctly return referenced files from triple slash", function () {
test("///<reference path = \"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "///<reference path=\"refFile3.ts\" />" + "\n" + "///<reference path= \"..\\refFile4d.ts\" />", true,
test("///<reference path = \"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "///<reference path=\"refFile3.ts\" />" + "\n" + "///<reference path= \"..\\refFile4d.ts\" />",
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 37 }, { fileName: "refFile2.ts", pos: 38, end: 73 },
{ fileName: "refFile3.ts", pos: 74, end: 109 }, { fileName: "..\\refFile4d.ts", pos: 110, end: 150 }],
@ -56,7 +58,9 @@ describe('PreProcessFile:', function () {
}),
it("Do not return reference path because of invalid triple-slash syntax", function () {
test("///<reference path\"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\">" + "\n" + "///<referencepath=\"refFile3.ts\" />" + "\n" + "///<reference pat= \"refFile4d.ts\" />", true,
test("///<reference path\"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\">" + "\n" + "///<referencepath=\"refFile3.ts\" />" + "\n" + "///<reference pat= \"refFile4d.ts\" />",
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: <ts.FileReference[]>[],
importedFiles: <ts.FileReference[]>[],
@ -66,7 +70,9 @@ describe('PreProcessFile:', function () {
}),
it("Correctly return imported files", function () {
test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");", true,
test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");",
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: <ts.FileReference[]>[],
importedFiles: [{ fileName: "r1.ts", pos: 20, end: 25 }, { fileName: "r2.ts", pos: 49, end: 54 }, { fileName: "r3.ts", pos: 78, end: 83 },
@ -77,7 +83,9 @@ describe('PreProcessFile:', function () {
}),
it("Do not return imported files if readImportFiles argument is false", function () {
test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");", false,
test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");",
/* readImports */ false,
/* detectJavaScriptImports */ false,
{
referencedFiles: <ts.FileReference[]>[],
importedFiles: <ts.FileReference[]>[],
@ -87,7 +95,9 @@ describe('PreProcessFile:', function () {
}),
it("Do not return import path because of invalid import syntax", function () {
test("import i1 require(\"r1.ts\"); import = require(\"r2.ts\") import i3= require(\"r3.ts\"); import i5", true,
test("import i1 require(\"r1.ts\"); import = require(\"r2.ts\") import i3= require(\"r3.ts\"); import i5",
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: <ts.FileReference[]>[],
importedFiles: [{ fileName: "r3.ts", pos: 73, end: 78 }],
@ -97,7 +107,9 @@ describe('PreProcessFile:', function () {
}),
it("Correctly return referenced files and import files", function () {
test("///<reference path=\"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\");", true,
test("///<reference path=\"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\");",
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 35 }, { fileName: "refFile2.ts", pos: 36, end: 71 }],
importedFiles: [{ fileName: "r1.ts", pos: 92, end: 97 }, { fileName: "r2.ts", pos: 121, end: 126 }],
@ -107,7 +119,9 @@ describe('PreProcessFile:', function () {
}),
it("Correctly return referenced files and import files even with some invalid syntax", function () {
test("///<reference path=\"refFile1.ts\" />" + "\n" + "///<reference path \"refFile2.ts\"/>" + "\n" + "import i1 = require(\"r1.ts\"); import = require(\"r2.ts\"); import i2 = require(\"r3.ts\");", true,
test("///<reference path=\"refFile1.ts\" />" + "\n" + "///<reference path \"refFile2.ts\"/>" + "\n" + "import i1 = require(\"r1.ts\"); import = require(\"r2.ts\"); import i2 = require(\"r3.ts\");",
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 35 }],
importedFiles: [{ fileName: "r1.ts", pos: 91, end: 96 }, { fileName: "r3.ts", pos: 148, end: 153 }],
@ -124,7 +138,8 @@ describe('PreProcessFile:', function () {
"import {a as A} from \"m5\";" + "\n" +
"import {a as A, b, c as C} from \"m6\";" + "\n" +
"import def , {a, b, c as C} from \"m7\";" + "\n",
true,
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: [],
importedFiles: [
@ -146,7 +161,8 @@ describe('PreProcessFile:', function () {
"export {a} from \"m2\";" + "\n" +
"export {a as A} from \"m3\";" + "\n" +
"export {a as A, b, c as C} from \"m4\";" + "\n",
true,
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: [],
importedFiles: [
@ -166,7 +182,11 @@ describe('PreProcessFile:', function () {
declare module "B" {}
function foo() {
}
`, false, {
`,
/* readImports */ false,
/* detectJavaScriptImports */ false,
{
referencedFiles: [],
importedFiles: [],
ambientExternalModules: ["B"],
@ -176,7 +196,8 @@ describe('PreProcessFile:', function () {
it("Correctly handles export import declarations", function () {
test("export import a = require(\"m1\");",
true,
/* readImports */true,
/* detectJavaScriptImports */ false,
{
referencedFiles: [],
importedFiles: [
@ -186,7 +207,61 @@ describe('PreProcessFile:', function () {
isLibFile: false
})
});
it("Correctly handles export require calls in JavaScript files", function () {
test(`
export import a = require("m1");
var x = require('m2');
foo(require('m3'));
var z = { f: require('m4') }
`,
/* readImports */true,
/* detectJavaScriptImports */ true,
{
referencedFiles: [],
importedFiles: [
{ fileName: "m1", pos: 39, end: 41 },
{ fileName: "m2", pos: 74, end: 76 },
{ fileName: "m3", pos: 105, end: 107 },
{ fileName: "m4", pos: 146, end: 148 },
],
ambientExternalModules: undefined,
isLibFile: false
})
});
it("Correctly handles dependency lists in define([deplist]) calls in JavaScript files", function () {
test(`
define(["mod1", "mod2"], (m1, m2) => {
});
`,
/* readImports */true,
/* detectJavaScriptImports */ true,
{
referencedFiles: [],
importedFiles: [
{ fileName: "mod1", pos: 21, end: 25 },
{ fileName: "mod2", pos: 29, end: 33 },
],
ambientExternalModules: undefined,
isLibFile: false
})
});
it("Correctly handles dependency lists in define(modName, [deplist]) calls in JavaScript files", function () {
test(`
define("mod", ["mod1", "mod2"], (m1, m2) => {
});
`,
/* readImports */true,
/* detectJavaScriptImports */ true,
{
referencedFiles: [],
importedFiles: [
{ fileName: "mod1", pos: 28, end: 32 },
{ fileName: "mod2", pos: 36, end: 40 },
],
ambientExternalModules: undefined,
isLibFile: false
})
});
});
});

View File

@ -5,7 +5,7 @@ import fs = require("fs");
import path = require("path");
import url = require("url");
import child_process = require("child_process");
import os = require('os');
import os = require("os");
/// Command line processing ///
@ -276,7 +276,6 @@ if ((browser && browser === 'chrome')) {
console.log(`default Chrome location is unknown for platform '${os.platform()}'`);
break;
}
if (fs.existsSync(defaultChromePath)) {
browserPath = defaultChromePath;
} else {