Merge branch 'master' into map4

This commit is contained in:
Andy Hanson 2016-10-27 12:25:25 -07:00
commit 867093707b
36 changed files with 552 additions and 152 deletions

View File

@ -1071,7 +1071,7 @@ namespace ts {
if (moduleSymbol) {
let exportDefaultSymbol: Symbol;
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
if (isUntypedModuleSymbol(moduleSymbol)) {
exportDefaultSymbol = moduleSymbol;
}
else {
@ -1151,7 +1151,7 @@ namespace ts {
if (targetSymbol) {
const name = specifier.propertyName || specifier.name;
if (name.text) {
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
if (isUntypedModuleSymbol(moduleSymbol)) {
return moduleSymbol;
}
@ -1371,8 +1371,9 @@ namespace ts {
}
const isRelative = isExternalModuleNameRelative(moduleName);
const quotedName = '"' + moduleName + '"';
if (!isRelative) {
const symbol = getSymbol(globals, '"' + moduleName + '"', SymbolFlags.ValueModule);
const symbol = getSymbol(globals, quotedName, SymbolFlags.ValueModule);
if (symbol) {
// merged symbol is module declaration symbol combined with all augmentations
return getMergedSymbol(symbol);
@ -1401,6 +1402,28 @@ namespace ts {
}
}
// May be an untyped module. If so, ignore resolutionDiagnostic.
if (!isRelative && resolvedModule && !extensionIsTypeScript(resolvedModule.extension)) {
if (compilerOptions.noImplicitAny) {
if (moduleNotFoundError) {
error(errorNode,
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
moduleReference,
resolvedModule.resolvedFileName);
}
return undefined;
}
// Create a new symbol to represent the untyped module and store it in globals.
// This provides a name to the module. See the test tests/cases/fourslash/untypedModuleImport.ts
const newSymbol = createSymbol(SymbolFlags.ValueModule, quotedName);
// Module symbols are expected to have 'exports', although since this is an untyped module it can be empty.
newSymbol.exports = new StringMap<Symbol>();
// Cache it so subsequent accesses will return the same module.
globals.set(quotedName, newSymbol);
return newSymbol;
}
if (moduleNotFoundError) {
// report errors only if it was requested
if (resolutionDiagnostic) {
@ -3470,7 +3493,7 @@ namespace ts {
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
if (symbol.valueDeclaration.kind === SyntaxKind.ModuleDeclaration && isShorthandAmbientModuleSymbol(symbol)) {
if (symbol.flags & SymbolFlags.Module && isUntypedModuleSymbol(symbol)) {
links.type = anyType;
}
else {
@ -19014,7 +19037,7 @@ namespace ts {
function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean {
let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression);
if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) {
if (!moduleSymbol || isUntypedModuleSymbol(moduleSymbol)) {
// If the module is not found or is shorthand, assume that it may export a value.
return true;
}
@ -19514,7 +19537,7 @@ namespace ts {
(typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective);
}
else {
// found at least one entry that does not originate from type reference directive
// found at least one entry that does not originate from type reference directive
return undefined;
}
}

View File

@ -99,6 +99,13 @@ namespace ts {
return undefined;
}
export function zipWith<T, U>(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void {
Debug.assert(arrayA.length === arrayB.length);
for (let i = 0; i < arrayA.length; i++) {
callback(arrayA[i], arrayB[i], i);
}
}
/**
* Iterates through `array` by index and performs the callback on each element of array until the callback
* returns a falsey value, then returns false.

View File

@ -2901,6 +2901,10 @@
"category": "Error",
"code": 7015
},
"Could not find a declaration file for module '{0}'. '{1}' implicitly has an 'any' type.": {
"category": "Error",
"code": 7016
},
"Index signature of object type implicitly has an 'any' type.": {
"category": "Error",
"code": 7017

View File

@ -45,7 +45,7 @@ namespace ts {
}
/** Adds `isExernalLibraryImport` to a Resolved to get a ResolvedModule. */
function resolvedModuleFromResolved({ path, extension }: Resolved, isExternalLibraryImport: boolean): ResolvedModule {
function resolvedModuleFromResolved({ path, extension }: Resolved, isExternalLibraryImport: boolean): ResolvedModuleFull {
return { resolvedFileName: path, extension, isExternalLibraryImport };
}

View File

@ -329,16 +329,16 @@ namespace ts {
// Map storing if there is emit blocking diagnostics for given input
const hasEmitBlockingDiagnostics = createFileMap<boolean>(getCanonicalFileName);
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModule[];
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
if (host.resolveModuleNames) {
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile).map(resolved => {
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
if (!resolved || resolved.extension !== undefined) {
return resolved;
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
return resolved as ResolvedModuleFull;
}
resolved = clone(resolved);
resolved.extension = extensionFromPath(resolved.resolvedFileName);
return resolved;
const withExtension = clone(resolved) as ResolvedModuleFull;
withExtension.extension = extensionFromPath(resolved.resolvedFileName);
return withExtension;
});
}
else {
@ -1290,7 +1290,7 @@ namespace ts {
function processImportedModules(file: SourceFile) {
collectExternalModuleReferences(file);
if (file.imports.length || file.moduleAugmentations.length) {
file.resolvedModules = new StringMap<ResolvedModule>();
file.resolvedModules = new StringMap<ResolvedModuleFull>();
const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral);
const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory));
Debug.assert(resolutions.length === moduleNames.length);
@ -1317,6 +1317,7 @@ namespace ts {
// - it's not a top level JavaScript module that exceeded the search max
const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth;
// Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs')
// This may still end up being an untyped module -- the file won't be included but imports will be allowed.
const shouldAddFile = resolvedFileName && !getResolutionDiagnostic(options, resolution) && !options.noResolve && i < file.imports.length && !elideImport;
if (elideImport) {
@ -1564,22 +1565,29 @@ namespace ts {
/* @internal */
/**
* Returns a DiagnosticMessage if we can't use a resolved module due to its extension.
* Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
* The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
* This returns a diagnostic even if the module will be an untyped module.
*/
export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModule): DiagnosticMessage | undefined {
export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined {
switch (extension) {
case Extension.Ts:
case Extension.Dts:
// These are always allowed.
return undefined;
case Extension.Tsx:
return needJsx();
case Extension.Jsx:
return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set;
return needJsx() || needAllowJs();
case Extension.Js:
return options.allowJs ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_allowJs_is_not_set;
return needAllowJs();
}
function needJsx() {
return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set;
}
function needAllowJs() {
return options.allowJs ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_allowJs_is_not_set;
}
}
}

View File

@ -458,7 +458,7 @@ namespace ts {
ThisNodeHasError = 1 << 19, // If the parser encountered an error when parsing the code that created this node
JavaScriptFile = 1 << 20, // If node was parsed in a JavaScript
ThisNodeOrAnySubNodesHasError = 1 << 21, // If this node or any of its children had an error
HasAggregatedChildData = 1 << 22, // If we've computed data from children and cached it in this node
HasAggregatedChildData = 1 << 22, // If we've computed data from children and cached it in this node
BlockScoped = Let | Const,
@ -2118,7 +2118,7 @@ namespace ts {
// Stores a mapping 'external module reference text' -> 'resolved file name' | undefined
// It is used to resolve module names in the checker.
// Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead
/* @internal */ resolvedModules: Map<string, ResolvedModule>;
/* @internal */ resolvedModules: Map<string, ResolvedModuleFull>;
/* @internal */ resolvedTypeReferenceDirectiveNames: Map<string, ResolvedTypeReferenceDirective>;
/* @internal */ imports: LiteralExpression[];
/* @internal */ moduleAugmentations: LiteralExpression[];
@ -3387,14 +3387,11 @@ namespace ts {
* Module resolution will pick up tsx/jsx/js files even if '--jsx' and '--allowJs' are turned off.
* The Program will then filter results based on these flags.
*
* At least one of `resolvedTsFileName` or `resolvedJsFileName` must be defined,
* else resolution should just return `undefined` instead of a ResolvedModule.
* Prefer to return a `ResolvedModuleFull` so that the file type does not have to be inferred.
*/
export interface ResolvedModule {
/** Path of the file the module was resolved to. */
resolvedFileName: string;
/** Extension of resolvedFileName. This must match what's at the end of resolvedFileName. */
extension: Extension;
/**
* Denotes if 'resolvedFileName' is isExternalLibraryImport and thus should be a proper external module:
* - be a .d.ts file
@ -3404,6 +3401,18 @@ namespace ts {
isExternalLibraryImport?: boolean;
}
/**
* ResolvedModule with an explicitly provided `extension` property.
* Prefer this over `ResolvedModule`.
*/
export interface ResolvedModuleFull extends ResolvedModule {
/**
* Extension of resolvedFileName. This must match what's at the end of resolvedFileName.
* This is optional for backwards-compatibility, but will be added if not provided.
*/
extension: Extension;
}
export enum Extension {
Ts,
Tsx,
@ -3414,7 +3423,7 @@ namespace ts {
}
export interface ResolvedModuleWithFailedLookupLocations {
resolvedModule: ResolvedModule | undefined;
resolvedModule: ResolvedModuleFull | undefined;
failedLookupLocations: string[];
}

View File

@ -87,13 +87,13 @@ namespace ts {
return !!(sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules.get(moduleNameText));
}
export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModule {
export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModuleFull {
return hasResolvedModule(sourceFile, moduleNameText) ? sourceFile.resolvedModules.get(moduleNameText) : undefined;
}
export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModule): void {
export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull): void {
if (!sourceFile.resolvedModules) {
sourceFile.resolvedModules = new StringMap<ResolvedModule>();
sourceFile.resolvedModules = new StringMap<ResolvedModuleFull>();
}
sourceFile.resolvedModules.set(moduleNameText, resolvedModule);
@ -108,11 +108,7 @@ namespace ts {
}
/* @internal */
/**
* Considers two ResolvedModules equal if they have the same `resolvedFileName`.
* Thus `{ ts: foo, js: bar }` is equal to `{ ts: foo, js: baz }` because `ts` is preferred.
*/
export function moduleResolutionIsEqualTo(oldResolution: ResolvedModule, newResolution: ResolvedModule): boolean {
export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleFull, newResolution: ResolvedModuleFull): boolean {
return oldResolution.isExternalLibraryImport === newResolution.isExternalLibraryImport &&
oldResolution.extension === newResolution.extension &&
oldResolution.resolvedFileName === newResolution.resolvedFileName;
@ -406,8 +402,9 @@ namespace ts {
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
}
export function isShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean {
return isShorthandAmbientModule(moduleSymbol.valueDeclaration);
/** Given a symbol for a module, checks that it is either an untyped import or a shorthand ambient module. */
export function isUntypedModuleSymbol(moduleSymbol: Symbol): boolean {
return !moduleSymbol.valueDeclaration || isShorthandAmbientModule(moduleSymbol.valueDeclaration);
}
function isShorthandAmbientModule(node: Node): boolean {

View File

@ -438,9 +438,8 @@ namespace FourSlash {
private getAllDiagnostics(): ts.Diagnostic[] {
const diagnostics: ts.Diagnostic[] = [];
const fileNames = this.languageServiceAdapterHost.getFilenames();
for (let i = 0, n = fileNames.length; i < n; i++) {
diagnostics.push.apply(this.getDiagnostics(fileNames[i]));
for (const fileName of this.languageServiceAdapterHost.getFilenames()) {
diagnostics.push.apply(this.getDiagnostics(fileName));
}
return diagnostics;
@ -580,12 +579,12 @@ namespace FourSlash {
this.raiseError(`goToDefinitions failed - expected to find ${endMarkers.length} definitions but got ${definitions.length}`);
}
for (let i = 0; i < endMarkers.length; i++) {
const marker = this.getMarkerByName(endMarkers[i]), definition = definitions[i];
ts.zipWith(endMarkers, definitions, (endMarker, definition, i) => {
const marker = this.getMarkerByName(endMarker);
if (marker.fileName !== definition.fileName || marker.position !== definition.textSpan.start) {
this.raiseError(`goToDefinition failed for definition ${i}: expected ${marker.fileName} at ${marker.position}, got ${definition.fileName} at ${definition.textSpan.start}`);
}
}
});
}
public verifyGetEmitOutputForCurrentFile(expected: string): void {
@ -602,10 +601,10 @@ namespace FourSlash {
public verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void {
const emit = this.languageService.getEmitOutput(this.activeFile.fileName);
assert.equal(emit.outputFiles.length, expected.length, "Number of emit output files");
for (let i = 0; i < emit.outputFiles.length; i++) {
assert.equal(emit.outputFiles[i].name, expected[i].name, "FileName");
assert.equal(emit.outputFiles[i].text, expected[i].text, "Content");
}
ts.zipWith(emit.outputFiles, expected, (outputFile, expected) => {
assert.equal(outputFile.name, expected.name, "FileName");
assert.equal(outputFile.text, expected.text, "Content");
});
}
public verifyMemberListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
@ -668,9 +667,9 @@ namespace FourSlash {
const entries = this.getCompletionListAtCaret().entries;
assert.isTrue(items.length <= entries.length, `Amount of expected items in completion list [ ${items.length} ] is greater than actual number of items in list [ ${entries.length} ]`);
for (let i = 0; i < items.length; i++) {
assert.equal(entries[i].name, items[i], `Unexpected item in completion list`);
}
ts.zipWith(entries, items, (entry, item) => {
assert.equal(entry.name, item, `Unexpected item in completion list`);
});
}
public noItemsWithSameNameButDifferentKind(): void {
@ -690,15 +689,7 @@ namespace FourSlash {
this.raiseError("Member list is empty at Caret");
}
else if ((members && members.entries.length !== 0) && !negative) {
let errorMsg = "\n" + "Member List contains: [" + members.entries[0].name;
for (let i = 1; i < members.entries.length; i++) {
errorMsg += ", " + members.entries[i].name;
}
errorMsg += "]\n";
this.raiseError("Member list is not empty at Caret: " + errorMsg);
this.raiseError(`Member list is not empty at Caret:\nMember List contains: ${stringify(members.entries.map(e => e.name))}`);
}
}
@ -708,13 +699,8 @@ namespace FourSlash {
this.raiseError("Completion list is empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition);
}
else if (completions && completions.entries.length !== 0 && !negative) {
let errorMsg = "\n" + "Completion List contains: [" + completions.entries[0].name;
for (let i = 1; i < completions.entries.length; i++) {
errorMsg += ", " + completions.entries[i].name;
}
errorMsg += "]\n";
this.raiseError("Completion list is not empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition + errorMsg);
this.raiseError(`Completion list is not empty at caret at position ${this.activeFile.fileName} ${this.currentCaretPosition}\n` +
`Completion List contains: ${stringify(completions.entries.map(e => e.name))}`);
}
}
@ -888,8 +874,7 @@ namespace FourSlash {
}
private verifyReferencesWorker(references: ts.ReferenceEntry[], fileName: string, start: number, end: number, isWriteAccess?: boolean, isDefinition?: boolean) {
for (let i = 0; i < references.length; i++) {
const reference = references[i];
for (const reference of references) {
if (reference && reference.fileName === fileName && reference.textSpan.start === start && ts.textSpanEnd(reference.textSpan) === end) {
if (typeof isWriteAccess !== "undefined" && reference.isWriteAccess !== isWriteAccess) {
this.raiseError(`verifyReferencesAtPositionListContains failed - item isWriteAccess value does not match, actual: ${reference.isWriteAccess}, expected: ${isWriteAccess}.`);
@ -1007,16 +992,11 @@ namespace FourSlash {
ranges = ranges.sort((r1, r2) => r1.start - r2.start);
references = references.sort((r1, r2) => r1.textSpan.start - r2.textSpan.start);
for (let i = 0, n = ranges.length; i < n; i++) {
const reference = references[i];
const range = ranges[i];
if (reference.textSpan.start !== range.start ||
ts.textSpanEnd(reference.textSpan) !== range.end) {
ts.zipWith(references, ranges, (reference, range) => {
if (reference.textSpan.start !== range.start || ts.textSpanEnd(reference.textSpan) !== range.end) {
this.raiseError("Rename location results do not match.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + JSON.stringify(references));
}
}
});
}
else {
this.raiseError("Expected rename to succeed, but it actually failed.");
@ -1246,8 +1226,7 @@ namespace FourSlash {
const emitFiles: FourSlashFile[] = []; // List of FourSlashFile that has emitThisFile flag on
const allFourSlashFiles = this.testData.files;
for (let idx = 0; idx < allFourSlashFiles.length; idx++) {
const file = allFourSlashFiles[idx];
for (const file of allFourSlashFiles) {
if (file.fileOptions[metadataOptionNames.emitThisFile] === "true") {
// Find a file with the flag emitThisFile turned on
emitFiles.push(file);
@ -1272,8 +1251,8 @@ namespace FourSlash {
if (emitOutput.emitSkipped) {
resultString += "Diagnostics:" + Harness.IO.newLine();
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram());
for (let i = 0, n = diagnostics.length; i < n; i++) {
resultString += " " + diagnostics[0].messageText + Harness.IO.newLine();
for (const diagnostic of diagnostics) {
resultString += " " + diagnostic.messageText + Harness.IO.newLine();
}
}
@ -1339,8 +1318,7 @@ namespace FourSlash {
}
public printCurrentFileState(makeWhitespaceVisible = false, makeCaretVisible = true) {
for (let i = 0; i < this.testData.files.length; i++) {
const file = this.testData.files[i];
for (const file of this.testData.files) {
const active = (this.activeFile === file);
Harness.IO.log(`=== Script (${file.fileName}) ${(active ? "(active, cursor at |)" : "")} ===`);
let content = this.getFileContent(file.fileName);
@ -1575,10 +1553,10 @@ namespace FourSlash {
edits = edits.sort((a, b) => a.span.start - b.span.start);
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
const oldContent = this.getFileContent(this.activeFile.fileName);
for (let j = 0; j < edits.length; j++) {
this.languageServiceAdapterHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
this.updateMarkersForEdit(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
const change = (edits[j].span.start - ts.textSpanEnd(edits[j].span)) + edits[j].newText.length;
for (const edit of edits) {
this.languageServiceAdapterHost.editScript(fileName, edit.span.start + runningOffset, ts.textSpanEnd(edit.span) + runningOffset, edit.newText);
this.updateMarkersForEdit(fileName, edit.span.start + runningOffset, ts.textSpanEnd(edit.span) + runningOffset, edit.newText);
const change = (edit.span.start - ts.textSpanEnd(edit.span)) + edit.newText.length;
runningOffset += change;
// TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
// this.languageService.getScriptLexicalStructure(fileName);
@ -1916,10 +1894,7 @@ namespace FourSlash {
jsonMismatchString());
}
for (let i = 0; i < expected.length; i++) {
const expectedClassification = expected[i];
const actualClassification = actual[i];
ts.zipWith(expected, actual, (expectedClassification, actualClassification) => {
const expectedType: string = (<any>ts.ClassificationTypeNames)[expectedClassification.classificationType];
if (expectedType !== actualClassification.classificationType) {
this.raiseError("verifyClassifications failed - expected classifications type to be " +
@ -1949,7 +1924,7 @@ namespace FourSlash {
actualText +
jsonMismatchString());
}
}
});
function jsonMismatchString() {
return Harness.IO.newLine() +
@ -1994,13 +1969,11 @@ namespace FourSlash {
this.raiseError(`verifyOutliningSpans failed - expected total spans to be ${spans.length}, but was ${actual.length}`);
}
for (let i = 0; i < spans.length; i++) {
const expectedSpan = spans[i];
const actualSpan = actual[i];
ts.zipWith(spans, actual, (expectedSpan, actualSpan, i) => {
if (expectedSpan.start !== actualSpan.textSpan.start || expectedSpan.end !== ts.textSpanEnd(actualSpan.textSpan)) {
this.raiseError(`verifyOutliningSpans failed - span ${(i + 1)} expected: (${expectedSpan.start},${expectedSpan.end}), actual: (${actualSpan.textSpan.start},${ts.textSpanEnd(actualSpan.textSpan)})`);
}
}
});
}
public verifyTodoComments(descriptors: string[], spans: TextSpan[]) {
@ -2011,15 +1984,13 @@ namespace FourSlash {
this.raiseError(`verifyTodoComments failed - expected total spans to be ${spans.length}, but was ${actual.length}`);
}
for (let i = 0; i < spans.length; i++) {
const expectedSpan = spans[i];
const actualComment = actual[i];
ts.zipWith(spans, actual, (expectedSpan, actualComment, i) => {
const actualCommentSpan = ts.createTextSpan(actualComment.position, actualComment.message.length);
if (expectedSpan.start !== actualCommentSpan.start || expectedSpan.end !== ts.textSpanEnd(actualCommentSpan)) {
this.raiseError(`verifyOutliningSpans failed - span ${(i + 1)} expected: (${expectedSpan.start},${expectedSpan.end}), actual: (${actualCommentSpan.start},${ts.textSpanEnd(actualCommentSpan)})`);
}
}
});
}
private getCodeFixes(errorCode?: number) {
@ -2166,11 +2137,9 @@ namespace FourSlash {
public verifyNavigationItemsCount(expected: number, searchValue: string, matchKind?: string, fileName?: string) {
const items = this.languageService.getNavigateToItems(searchValue, /*maxResultCount*/ undefined, fileName);
let actual = 0;
let item: ts.NavigateToItem;
// Count only the match that match the same MatchKind
for (let i = 0; i < items.length; i++) {
item = items[i];
for (const item of items) {
if (!matchKind || item.matchKind === matchKind) {
actual++;
}
@ -2198,8 +2167,7 @@ namespace FourSlash {
this.raiseError("verifyNavigationItemsListContains failed - found 0 navigation items, expected at least one.");
}
for (let i = 0; i < items.length; i++) {
const item = items[i];
for (const item of items) {
if (item && item.name === name && item.kind === kind &&
(matchKind === undefined || item.matchKind === matchKind) &&
(fileName === undefined || item.fileName === fileName) &&
@ -2250,24 +2218,16 @@ namespace FourSlash {
public printNavigationItems(searchValue: string) {
const items = this.languageService.getNavigateToItems(searchValue);
const length = items && items.length;
Harness.IO.log(`NavigationItems list (${length} items)`);
for (let i = 0; i < length; i++) {
const item = items[i];
Harness.IO.log(`NavigationItems list (${items.length} items)`);
for (const item of items) {
Harness.IO.log(`name: ${item.name}, kind: ${item.kind}, parentName: ${item.containerName}, fileName: ${item.fileName}`);
}
}
public printNavigationBar() {
const items = this.languageService.getNavigationBarItems(this.activeFile.fileName);
const length = items && items.length;
Harness.IO.log(`Navigation bar (${length} items)`);
for (let i = 0; i < length; i++) {
const item = items[i];
Harness.IO.log(`Navigation bar (${items.length} items)`);
for (const item of items) {
Harness.IO.log(`${repeatString(item.indent, " ")}name: ${item.text}, kind: ${item.kind}, childItems: ${item.childItems.map(child => child.text)}`);
}
}
@ -2388,8 +2348,7 @@ namespace FourSlash {
}
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
for (const item of items) {
if (item.name === name) {
if (documentation != undefined || text !== undefined) {
const details = this.getCompletionEntryDetails(item.name);
@ -2438,20 +2397,17 @@ namespace FourSlash {
name = name.indexOf("/") === -1 ? (this.basePath + "/" + name) : name;
const availableNames: string[] = [];
let foundIt = false;
for (let i = 0; i < this.testData.files.length; i++) {
const fn = this.testData.files[i].fileName;
result = ts.forEach(this.testData.files, file => {
const fn = file.fileName;
if (fn) {
if (fn === name) {
result = this.testData.files[i];
foundIt = true;
break;
return file;
}
availableNames.push(fn);
}
}
});
if (!foundIt) {
if (!result) {
throw new Error(`No test file named "${name}" exists. Available file names are: ${availableNames.join(", ")}`);
}
}
@ -2552,8 +2508,8 @@ ${code}
function chompLeadingSpace(content: string) {
const lines = content.split("\n");
for (let i = 0; i < lines.length; i++) {
if ((lines[i].length !== 0) && (lines[i].charAt(0) !== " ")) {
for (const line of lines) {
if ((line.length !== 0) && (line.charAt(0) !== " ")) {
return content;
}
}
@ -2591,8 +2547,7 @@ ${code}
currentFileName = fileName;
}
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
for (let line of lines) {
const lineLength = line.length;
if (lineLength > 0 && line.charAt(lineLength - 1) === "\r") {

View File

@ -1108,22 +1108,7 @@ namespace Harness {
const option = getCommandLineOption(name);
if (option) {
const errors: ts.Diagnostic[] = [];
switch (option.type) {
case "boolean":
options[option.name] = value.toLowerCase() === "true";
break;
case "string":
options[option.name] = value;
break;
// If not a primitive, the possible types are specified in what is effectively a map of options.
case "list":
options[option.name] = ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
break;
default:
options[option.name] = ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
break;
}
options[option.name] = optionValue(option, value, errors);
if (errors.length > 0) {
throw new Error(`Unknown value '${value}' for compiler option '${name}'.`);
}
@ -1135,6 +1120,27 @@ namespace Harness {
}
}
function optionValue(option: ts.CommandLineOption, value: string, errors: ts.Diagnostic[]): any {
switch (option.type) {
case "boolean":
return value.toLowerCase() === "true";
case "string":
return value;
case "number": {
const number = parseInt(value, 10);
if (isNaN(number)) {
throw new Error(`Value must be a number, got: ${JSON.stringify(value)}`);
}
return number;
}
// If not a primitive, the possible types are specified in what is effectively a map of options.
case "list":
return ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
default:
return ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
}
}
export interface TestFile {
unitName: string;
content: string;

View File

@ -1,7 +1,7 @@
/// <reference path="..\harness.ts" />
namespace ts {
export function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): boolean {
export function checkResolvedModule(expected: ResolvedModuleFull, actual: ResolvedModuleFull): boolean {
if (!expected === !actual) {
if (expected) {
assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`);
@ -13,13 +13,13 @@ namespace ts {
return false;
}
export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModule, expectedFailedLookupLocations: string[]): void {
export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void {
assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved");
checkResolvedModule(actual.resolvedModule, expectedResolvedModule);
assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations);
}
export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ResolvedModule {
export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ResolvedModuleFull {
return { resolvedFileName, extension: extensionFromPath(resolvedFileName), isExternalLibraryImport };
}

View File

@ -151,7 +151,7 @@ namespace ts.server {
m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false);
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] {
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName,
m => m.resolvedModule, r => r.resolvedFileName, /*logChanges*/ true);
}

View File

@ -467,7 +467,7 @@ namespace ts {
public languageVariant: LanguageVariant;
public identifiers: Map<string, string>;
public nameTable: Map<string, number>;
public resolvedModules: Map<string, ResolvedModule>;
public resolvedModules: Map<string, ResolvedModuleFull>;
public resolvedTypeReferenceDirectiveNames: Map<string, ResolvedTypeReferenceDirective>;
public imports: LiteralExpression[];
public moduleAugmentations: LiteralExpression[];

View File

@ -316,7 +316,7 @@ namespace ts {
private loggingEnabled = false;
private tracingEnabled = false;
public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModule[];
public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModuleFull[];
public resolveTypeReferenceDirectives: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[];
public directoryExists: (directoryName: string) => boolean;

View File

@ -0,0 +1,12 @@
/a.ts(1,17): error TS6143: Module './jsx' was resolved to '/jsx.jsx', but '--allowJs' is not set.
==== /a.ts (1 errors) ====
import jsx from "./jsx";
~~~~~~~
!!! error TS6143: Module './jsx' was resolved to '/jsx.jsx', but '--allowJs' is not set.
==== /jsx.jsx (0 errors) ====
// Test the error message if we have `--jsx` but not `--allowJw`.

View File

@ -0,0 +1,12 @@
//// [tests/cases/compiler/moduleResolutionWithExtensions_notSupported3.ts] ////
//// [jsx.jsx]
// Test the error message if we have `--jsx` but not `--allowJw`.
//// [a.ts]
import jsx from "./jsx";
//// [a.js]
"use strict";

View File

@ -0,0 +1,17 @@
[
"======== Resolving module './jsx' from '/a.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"Loading module as file / folder, candidate module location '/jsx'.",
"File '/jsx.ts' does not exist.",
"File '/jsx.tsx' does not exist.",
"File '/jsx.d.ts' does not exist.",
"File '/jsx/package.json' does not exist.",
"File '/jsx/index.ts' does not exist.",
"File '/jsx/index.tsx' does not exist.",
"File '/jsx/index.d.ts' does not exist.",
"Loading module as file / folder, candidate module location '/jsx'.",
"File '/jsx.js' does not exist.",
"File '/jsx.jsx' exist - use it as a name resolution result.",
"Resolving real path for '/jsx.jsx', result '/jsx.jsx'",
"======== Module name './jsx' was successfully resolved to '/jsx.jsx'. ========"
]

View File

@ -0,0 +1,37 @@
//// [tests/cases/conformance/moduleResolution/untypedModuleImport.ts] ////
//// [index.js]
// This tests that importing from a JS file globally works in an untyped way.
// (Assuming we don't have `--noImplicitAny` or `--allowJs`.)
This file is not processed.
//// [a.ts]
import * as foo from "foo";
foo.bar();
//// [b.ts]
import foo = require("foo");
foo();
//// [c.ts]
import foo, { bar } from "foo";
import "./a";
import "./b";
foo(bar());
//// [a.js]
"use strict";
var foo = require("foo");
foo.bar();
//// [b.js]
"use strict";
var foo = require("foo");
foo();
//// [c.js]
"use strict";
var foo_1 = require("foo");
require("./a");
require("./b");
foo_1["default"](foo_1.bar());

View File

@ -0,0 +1,25 @@
=== /c.ts ===
import foo, { bar } from "foo";
>foo : Symbol(foo, Decl(c.ts, 0, 6))
>bar : Symbol(bar, Decl(c.ts, 0, 13))
import "./a";
import "./b";
foo(bar());
>foo : Symbol(foo, Decl(c.ts, 0, 6))
>bar : Symbol(bar, Decl(c.ts, 0, 13))
=== /a.ts ===
import * as foo from "foo";
>foo : Symbol(foo, Decl(a.ts, 0, 6))
foo.bar();
>foo : Symbol(foo, Decl(a.ts, 0, 6))
=== /b.ts ===
import foo = require("foo");
>foo : Symbol(foo, Decl(b.ts, 0, 0))
foo();
>foo : Symbol(foo, Decl(b.ts, 0, 0))

View File

@ -0,0 +1,31 @@
=== /c.ts ===
import foo, { bar } from "foo";
>foo : any
>bar : any
import "./a";
import "./b";
foo(bar());
>foo(bar()) : any
>foo : any
>bar() : any
>bar : any
=== /a.ts ===
import * as foo from "foo";
>foo : any
foo.bar();
>foo.bar() : any
>foo.bar : any
>foo : any
>bar : any
=== /b.ts ===
import foo = require("foo");
>foo : any
foo();
>foo() : any
>foo : any

View File

@ -0,0 +1,16 @@
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_allowJs.ts] ////
//// [index.js]
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
exports.default = { bar() { return 0; } }
//// [a.ts]
import foo from "foo";
foo.bar();
//// [a.js]
"use strict";
var foo_1 = require("foo");
foo_1["default"].bar();

View File

@ -0,0 +1,17 @@
=== /a.ts ===
import foo from "foo";
>foo : Symbol(foo, Decl(a.ts, 0, 6))
foo.bar();
>foo.bar : Symbol(bar, Decl(index.js, 2, 19))
>foo : Symbol(foo, Decl(a.ts, 0, 6))
>bar : Symbol(bar, Decl(index.js, 2, 19))
=== /node_modules/foo/index.js ===
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
exports.default = { bar() { return 0; } }
>exports : Symbol(default, Decl(index.js, 0, 0))
>default : Symbol(default, Decl(index.js, 0, 0))
>bar : Symbol(bar, Decl(index.js, 2, 19))

View File

@ -0,0 +1,22 @@
=== /a.ts ===
import foo from "foo";
>foo : { bar(): number; }
foo.bar();
>foo.bar() : number
>foo.bar : () => number
>foo : { bar(): number; }
>bar : () => number
=== /node_modules/foo/index.js ===
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
exports.default = { bar() { return 0; } }
>exports.default = { bar() { return 0; } } : { bar(): number; }
>exports.default : any
>exports : any
>default : any
>{ bar() { return 0; } } : { bar(): number; }
>bar : () => number
>0 : 0

View File

@ -0,0 +1,13 @@
/a.ts(1,22): error TS7016: Could not find a declaration file for module 'foo'. '/node_modules/foo/index.js' implicitly has an 'any' type.
==== /a.ts (1 errors) ====
import * as foo from "foo";
~~~~~
!!! error TS7016: Could not find a declaration file for module 'foo'. '/node_modules/foo/index.js' implicitly has an 'any' type.
==== /node_modules/foo/index.js (0 errors) ====
// This tests that `--noImplicitAny` disables untyped modules.
This file is not processed.

View File

@ -0,0 +1,13 @@
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts] ////
//// [index.js]
// This tests that `--noImplicitAny` disables untyped modules.
This file is not processed.
//// [a.ts]
import * as foo from "foo";
//// [a.js]
"use strict";

View File

@ -0,0 +1,13 @@
/a.ts(1,22): error TS6143: Module './foo' was resolved to '/foo.js', but '--allowJs' is not set.
==== /a.ts (1 errors) ====
import * as foo from "./foo";
~~~~~~~
!!! error TS6143: Module './foo' was resolved to '/foo.js', but '--allowJs' is not set.
==== /foo.js (0 errors) ====
// This tests that untyped module imports don't happen with local imports.
This file is not processed.

View File

@ -0,0 +1,13 @@
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noLocalImports.ts] ////
//// [foo.js]
// This tests that untyped module imports don't happen with local imports.
This file is not processed.
//// [a.ts]
import * as foo from "./foo";
//// [a.js]
"use strict";

View File

@ -0,0 +1,23 @@
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_vsAmbient.ts] ////
//// [index.js]
// This tests that an ambient module declaration overrides an untyped import.
This file is not processed.
//// [declarations.d.ts]
declare module "foo" {
export const x: number;
}
//// [a.ts]
/// <reference path="./declarations.d.ts" />
import { x } from "foo";
x;
//// [a.js]
"use strict";
/// <reference path="./declarations.d.ts" />
var foo_1 = require("foo");
foo_1.x;

View File

@ -0,0 +1,14 @@
=== /a.ts ===
/// <reference path="./declarations.d.ts" />
import { x } from "foo";
>x : Symbol(x, Decl(a.ts, 1, 8))
x;
>x : Symbol(x, Decl(a.ts, 1, 8))
=== /declarations.d.ts ===
declare module "foo" {
export const x: number;
>x : Symbol(x, Decl(declarations.d.ts, 1, 16))
}

View File

@ -0,0 +1,14 @@
=== /a.ts ===
/// <reference path="./declarations.d.ts" />
import { x } from "foo";
>x : number
x;
>x : number
=== /declarations.d.ts ===
declare module "foo" {
export const x: number;
>x : number
}

View File

@ -0,0 +1,9 @@
// @noImplicitReferences: true
// @jsx: preserve
// @traceResolution: true
// Test the error message if we have `--jsx` but not `--allowJw`.
// @Filename: /jsx.jsx
// @Filename: /a.ts
import jsx from "./jsx";

View File

@ -0,0 +1,21 @@
// @noImplicitReferences: true
// @currentDirectory: /
// This tests that importing from a JS file globally works in an untyped way.
// (Assuming we don't have `--noImplicitAny` or `--allowJs`.)
// @filename: /node_modules/foo/index.js
This file is not processed.
// @filename: /a.ts
import * as foo from "foo";
foo.bar();
// @filename: /b.ts
import foo = require("foo");
foo();
// @filename: /c.ts
import foo, { bar } from "foo";
import "./a";
import "./b";
foo(bar());

View File

@ -0,0 +1,12 @@
// @noImplicitReferences: true
// @currentDirectory: /
// @allowJs: true
// @maxNodeModuleJsDepth: 1
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
// @filename: /node_modules/foo/index.js
exports.default = { bar() { return 0; } }
// @filename: /a.ts
import foo from "foo";
foo.bar();

View File

@ -0,0 +1,10 @@
// @noImplicitReferences: true
// @currentDirectory: /
// @noImplicitAny: true
// This tests that `--noImplicitAny` disables untyped modules.
// @filename: /node_modules/foo/index.js
This file is not processed.
// @filename: /a.ts
import * as foo from "foo";

View File

@ -0,0 +1,9 @@
// @noImplicitReferences: true
// @currentDirectory: /
// This tests that untyped module imports don't happen with local imports.
// @filename: /foo.js
This file is not processed.
// @filename: /a.ts
import * as foo from "./foo";

View File

@ -0,0 +1,16 @@
// @noImplicitReferences: true
// @currentDirectory: /
// This tests that an ambient module declaration overrides an untyped import.
// @filename: /node_modules/foo/index.js
This file is not processed.
// @filename: /declarations.d.ts
declare module "foo" {
export const x: number;
}
// @filename: /a.ts
/// <reference path="./declarations.d.ts" />
import { x } from "foo";
x;

View File

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
// @Filename: node_modules/foo/index.js
////{}
// @Filename: a.ts
////import /*foo*/[|foo|] from /*fooModule*/"foo";
////[|foo|]();
goTo.file("a.ts");
debug.printErrorList();
verify.numberOfErrorsInCurrentFile(0);
goTo.marker("fooModule");
verify.goToDefinitionIs([]);
verify.quickInfoIs('module "foo"');
verify.referencesAre([])
goTo.marker("foo");
verify.goToDefinitionIs([]);
verify.quickInfoIs("import foo");
verify.rangesReferenceEachOther();