Code fix for missing imports (#11768)
* Add codefix for missing imports + tests * Re-order and cleanup * refactor * make tests pass * Make import specifier for new imports more comprehensive * Fix existing import cases * refactor * Fix multiple import statement case * add multiple code fixes and code action filtering and polishing * not using the generic verify method for import fixes. * Correct insert position for new imports * improve the code action filtering logic * Fix line ending issue * cache where we can
This commit is contained in:
parent
0b0b68e79a
commit
52ec508e27
|
@ -104,10 +104,10 @@ namespace ts {
|
|||
getEmitResolver,
|
||||
getExportsOfModule: getExportsOfModuleAsArray,
|
||||
getAmbientModules,
|
||||
|
||||
getJsxElementAttributesType,
|
||||
getJsxIntrinsicTagNames,
|
||||
isOptionalParameter,
|
||||
tryGetMemberInModuleExports,
|
||||
tryFindAmbientModuleWithoutAugmentations: moduleName => {
|
||||
// we deliberately exclude augmentations
|
||||
// since we are only interested in declarations of the module itself
|
||||
|
@ -1483,6 +1483,13 @@ namespace ts {
|
|||
return symbolsToArray(getExportsOfModule(moduleSymbol));
|
||||
}
|
||||
|
||||
function tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined {
|
||||
const symbolTable = getExportsOfModule(moduleSymbol);
|
||||
if (symbolTable) {
|
||||
return symbolTable[memberName];
|
||||
}
|
||||
}
|
||||
|
||||
function getExportsOfSymbol(symbol: Symbol): SymbolTable {
|
||||
return symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : symbol.exports || emptySymbols;
|
||||
}
|
||||
|
|
|
@ -1391,6 +1391,14 @@ namespace ts {
|
|||
getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 ? ModuleKind.ES2015 : ModuleKind.CommonJS;
|
||||
}
|
||||
|
||||
export function getEmitModuleResolutionKind(compilerOptions: CompilerOptions) {
|
||||
let moduleResolution = compilerOptions.moduleResolution;
|
||||
if (moduleResolution === undefined) {
|
||||
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
|
||||
}
|
||||
return moduleResolution;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
let seenAsterisk = false;
|
||||
|
|
|
@ -3190,5 +3190,17 @@
|
|||
"Type '{0}' is not assignable to type '{1}'. Two different types with this name exist, but they are unrelated.": {
|
||||
"category": "Error",
|
||||
"code": 90010
|
||||
},
|
||||
"Import {0} from {1}": {
|
||||
"category": "Message",
|
||||
"code": 90013
|
||||
},
|
||||
"Change {0} to {1}": {
|
||||
"category": "Message",
|
||||
"code": 90014
|
||||
},
|
||||
"Add {0} to existing import declaration from {1}": {
|
||||
"category": "Message",
|
||||
"code": 90015
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace ts {
|
|||
return { resolvedModule: resolved && resolvedModuleFromResolved(resolved, isExternalLibraryImport), failedLookupLocations };
|
||||
}
|
||||
|
||||
function moduleHasNonRelativeName(moduleName: string): boolean {
|
||||
export function moduleHasNonRelativeName(moduleName: string): boolean {
|
||||
return !(isRootedDiskPath(moduleName) || isExternalModuleNameRelative(moduleName));
|
||||
}
|
||||
|
||||
|
|
|
@ -2371,6 +2371,8 @@ namespace ts {
|
|||
isOptionalParameter(node: ParameterDeclaration): boolean;
|
||||
getAmbientModules(): Symbol[];
|
||||
|
||||
tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined;
|
||||
|
||||
/* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol;
|
||||
|
||||
// Should not be called directly. Should only be accessed through the Program instance.
|
||||
|
@ -3190,7 +3192,7 @@ namespace ts {
|
|||
target?: ScriptTarget;
|
||||
traceResolution?: boolean;
|
||||
types?: string[];
|
||||
/** Paths used to used to compute primary types search locations */
|
||||
/** Paths used to compute primary types search locations */
|
||||
typeRoots?: string[];
|
||||
/*@internal*/ version?: boolean;
|
||||
/*@internal*/ watch?: boolean;
|
||||
|
|
|
@ -2046,6 +2046,34 @@ namespace FourSlash {
|
|||
}
|
||||
}
|
||||
|
||||
public verifyImportFixAtPosition(expectedTextArray: string[], errorCode?: number) {
|
||||
const ranges = this.getRanges();
|
||||
if (ranges.length == 0) {
|
||||
this.raiseError("At least one range should be specified in the testfile.");
|
||||
}
|
||||
|
||||
const codeFixes = this.getCodeFixes(errorCode);
|
||||
|
||||
if (!codeFixes || codeFixes.length == 0) {
|
||||
this.raiseError("No codefixes returned.");
|
||||
}
|
||||
|
||||
const actualTextArray: string[] = [];
|
||||
const scriptInfo = this.languageServiceAdapterHost.getScriptInfo(codeFixes[0].changes[0].fileName);
|
||||
const originalContent = scriptInfo.content;
|
||||
for (const codeFix of codeFixes) {
|
||||
this.applyEdits(codeFix.changes[0].fileName, codeFix.changes[0].textChanges, /*isFormattingEdit*/ false);
|
||||
actualTextArray.push(this.normalizeNewlines(this.rangeText(ranges[0])));
|
||||
scriptInfo.updateContent(originalContent);
|
||||
}
|
||||
const sortedExpectedArray = ts.map(expectedTextArray, str => this.normalizeNewlines(str)).sort();
|
||||
const sortedActualArray = actualTextArray.sort();
|
||||
if (!ts.arrayIsEqualTo(sortedExpectedArray, sortedActualArray)) {
|
||||
this.raiseError(
|
||||
`Actual text array doesn't match expected text array. \nActual: \n"${sortedActualArray.join("\n\n")}"\n---\nExpected: \n'${sortedExpectedArray.join("\n\n")}'`);
|
||||
}
|
||||
}
|
||||
|
||||
public verifyDocCommentTemplate(expected?: ts.TextInsertion) {
|
||||
const name = "verifyDocCommentTemplate";
|
||||
const actual = this.languageService.getDocCommentTemplateAtPosition(this.activeFile.fileName, this.currentCaretPosition);
|
||||
|
@ -2079,6 +2107,10 @@ namespace FourSlash {
|
|||
});
|
||||
}
|
||||
|
||||
private normalizeNewlines(str: string) {
|
||||
return str.replace(/\r?\n/g, "\n");
|
||||
}
|
||||
|
||||
public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) {
|
||||
|
||||
const openBraceMap = ts.createMap<ts.CharacterCodes>({
|
||||
|
@ -2606,7 +2638,7 @@ ${code}
|
|||
resetLocalData();
|
||||
}
|
||||
|
||||
currentFileName = basePath + "/" + value;
|
||||
currentFileName = ts.isRootedDiskPath(value) ? value : basePath + "/" + value;
|
||||
currentFileOptions[key] = value;
|
||||
}
|
||||
else {
|
||||
|
@ -3303,6 +3335,10 @@ namespace FourSlashInterface {
|
|||
this.state.verifyCodeFixAtPosition(expectedText, errorCode);
|
||||
}
|
||||
|
||||
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
|
||||
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
|
||||
}
|
||||
|
||||
public navigationBar(json: any) {
|
||||
this.state.verifyNavigationBar(json);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* @internal */
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export interface CodeFix {
|
||||
errorCodes: number[];
|
||||
|
@ -11,6 +11,8 @@ namespace ts {
|
|||
span: TextSpan;
|
||||
program: Program;
|
||||
newLineCharacter: string;
|
||||
host: LanguageServiceHost;
|
||||
cancellationToken: CancellationToken;
|
||||
}
|
||||
|
||||
export namespace codefix {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
///<reference path='superFixes.ts' />
|
||||
///<reference path='importFixes.ts' />
|
||||
///<reference path='unusedIdentifierFixes.ts' />
|
591
src/services/codefixes/importFixes.ts
Normal file
591
src/services/codefixes/importFixes.ts
Normal file
|
@ -0,0 +1,591 @@
|
|||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
|
||||
type ImportCodeActionKind = "CodeChange" | "InsertingIntoExistingImport" | "NewImport";
|
||||
interface ImportCodeAction extends CodeAction {
|
||||
kind: ImportCodeActionKind,
|
||||
moduleSpecifier?: string
|
||||
}
|
||||
|
||||
enum ModuleSpecifierComparison {
|
||||
Better,
|
||||
Equal,
|
||||
Worse
|
||||
}
|
||||
|
||||
class ImportCodeActionMap {
|
||||
private symbolIdToActionMap = createMap<ImportCodeAction[]>();
|
||||
|
||||
addAction(symbolId: number, newAction: ImportCodeAction) {
|
||||
if (!newAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.symbolIdToActionMap[symbolId]) {
|
||||
this.symbolIdToActionMap[symbolId] = [newAction];
|
||||
return;
|
||||
}
|
||||
|
||||
if (newAction.kind === "CodeChange") {
|
||||
this.symbolIdToActionMap[symbolId].push(newAction);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedNewImports: ImportCodeAction[] = [];
|
||||
for (const existingAction of this.symbolIdToActionMap[symbolId]) {
|
||||
if (existingAction.kind === "CodeChange") {
|
||||
// only import actions should compare
|
||||
updatedNewImports.push(existingAction);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (this.compareModuleSpecifiers(existingAction.moduleSpecifier, newAction.moduleSpecifier)) {
|
||||
case ModuleSpecifierComparison.Better:
|
||||
// the new one is not worth considering if it is a new improt.
|
||||
// However if it is instead a insertion into existing import, the user might want to use
|
||||
// the module specifier even it is worse by our standards. So keep it.
|
||||
if (newAction.kind === "NewImport") {
|
||||
return;
|
||||
}
|
||||
case ModuleSpecifierComparison.Equal:
|
||||
// the current one is safe. But it is still possible that the new one is worse
|
||||
// than another existing one. For example, you may have new imports from "./foo/bar"
|
||||
// and "bar", when the new one is "bar/bar2" and the current one is "./foo/bar". The new
|
||||
// one and the current one are not comparable (one relative path and one absolute path),
|
||||
// but the new one is worse than the other one, so should not add to the list.
|
||||
updatedNewImports.push(existingAction);
|
||||
break;
|
||||
case ModuleSpecifierComparison.Worse:
|
||||
// the existing one is worse, remove from the list.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// if we reach here, it means the new one is better or equal to all of the existing ones.
|
||||
updatedNewImports.push(newAction);
|
||||
this.symbolIdToActionMap[symbolId] = updatedNewImports;
|
||||
}
|
||||
|
||||
addActions(symbolId: number, newActions: ImportCodeAction[]) {
|
||||
for (const newAction of newActions) {
|
||||
this.addAction(symbolId, newAction);
|
||||
}
|
||||
}
|
||||
|
||||
getAllActions() {
|
||||
let result: ImportCodeAction[] = [];
|
||||
for (const symbolId in this.symbolIdToActionMap) {
|
||||
result = concatenate(result, this.symbolIdToActionMap[symbolId]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private compareModuleSpecifiers(moduleSpecifier1: string, moduleSpecifier2: string): ModuleSpecifierComparison {
|
||||
if (moduleSpecifier1 === moduleSpecifier2) {
|
||||
return ModuleSpecifierComparison.Equal;
|
||||
}
|
||||
|
||||
// if moduleSpecifier1 (ms1) is a substring of ms2, then it is better
|
||||
if (moduleSpecifier2.indexOf(moduleSpecifier1) === 0) {
|
||||
return ModuleSpecifierComparison.Better;
|
||||
}
|
||||
|
||||
if (moduleSpecifier1.indexOf(moduleSpecifier2) === 0) {
|
||||
return ModuleSpecifierComparison.Worse;
|
||||
}
|
||||
|
||||
// if both are relative paths, and ms1 has fewer levels, then it is better
|
||||
if (isExternalModuleNameRelative(moduleSpecifier1) && isExternalModuleNameRelative(moduleSpecifier2)) {
|
||||
const regex = new RegExp(directorySeparator, "g");
|
||||
const moduleSpecifier1LevelCount = (moduleSpecifier1.match(regex) || []).length;
|
||||
const moduleSpecifier2LevelCount = (moduleSpecifier2.match(regex) || []).length;
|
||||
|
||||
return moduleSpecifier1LevelCount < moduleSpecifier2LevelCount
|
||||
? ModuleSpecifierComparison.Better
|
||||
: moduleSpecifier1LevelCount === moduleSpecifier2LevelCount
|
||||
? ModuleSpecifierComparison.Equal
|
||||
: ModuleSpecifierComparison.Worse;
|
||||
}
|
||||
|
||||
// the equal cases include when the two specifiers are not comparable.
|
||||
return ModuleSpecifierComparison.Equal;
|
||||
}
|
||||
}
|
||||
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Cannot_find_name_0.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const checker = context.program.getTypeChecker();
|
||||
const allSourceFiles = context.program.getSourceFiles();
|
||||
const useCaseSensitiveFileNames = context.host.useCaseSensitiveFileNames ? context.host.useCaseSensitiveFileNames() : false;
|
||||
|
||||
const token = getTokenAtPosition(sourceFile, context.span.start);
|
||||
const name = token.getText();
|
||||
const symbolIdActionMap = new ImportCodeActionMap();
|
||||
|
||||
// this is a module id -> module import declaration map
|
||||
const cachedImportDeclarations = createMap<(ImportDeclaration | ImportEqualsDeclaration)[]>();
|
||||
let cachedNewImportInsertPosition: number;
|
||||
|
||||
const allPotentialModules = checker.getAmbientModules();
|
||||
for (const otherSourceFile of allSourceFiles) {
|
||||
if (otherSourceFile !== sourceFile && isExternalOrCommonJsModule(otherSourceFile)) {
|
||||
allPotentialModules.push(otherSourceFile.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
const currentTokenMeaning = getMeaningFromLocation(token);
|
||||
for (const moduleSymbol of allPotentialModules) {
|
||||
context.cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
// check the default export
|
||||
const defaultExport = checker.tryGetMemberInModuleExports("default", moduleSymbol);
|
||||
if (defaultExport) {
|
||||
const localSymbol = getLocalSymbolForExportDefault(defaultExport);
|
||||
if (localSymbol && localSymbol.name === name && checkSymbolHasMeaning(localSymbol, currentTokenMeaning)) {
|
||||
// check if this symbol is already used
|
||||
const symbolId = getUniqueSymbolId(localSymbol);
|
||||
symbolIdActionMap.addActions(symbolId, getCodeActionForImport(moduleSymbol, /*isDefaultExport*/ true));
|
||||
}
|
||||
}
|
||||
|
||||
// check exports with the same name
|
||||
const exportSymbolWithIdenticalName = checker.tryGetMemberInModuleExports(name, moduleSymbol);
|
||||
if (exportSymbolWithIdenticalName && checkSymbolHasMeaning(exportSymbolWithIdenticalName, currentTokenMeaning)) {
|
||||
const symbolId = getUniqueSymbolId(exportSymbolWithIdenticalName);
|
||||
symbolIdActionMap.addActions(symbolId, getCodeActionForImport(moduleSymbol));
|
||||
}
|
||||
}
|
||||
|
||||
return symbolIdActionMap.getAllActions();
|
||||
|
||||
function getImportDeclarations(moduleSymbol: Symbol) {
|
||||
const moduleSymbolId = getUniqueSymbolId(moduleSymbol);
|
||||
|
||||
if (cachedImportDeclarations[moduleSymbolId]) {
|
||||
return cachedImportDeclarations[moduleSymbolId];
|
||||
}
|
||||
|
||||
const existingDeclarations: (ImportDeclaration | ImportEqualsDeclaration)[] = [];
|
||||
for (const importModuleSpecifier of sourceFile.imports) {
|
||||
const importSymbol = checker.getSymbolAtLocation(importModuleSpecifier);
|
||||
if (importSymbol === moduleSymbol) {
|
||||
existingDeclarations.push(getImportDeclaration(importModuleSpecifier));
|
||||
}
|
||||
}
|
||||
cachedImportDeclarations[moduleSymbolId] = existingDeclarations;
|
||||
return existingDeclarations;
|
||||
|
||||
function getImportDeclaration(moduleSpecifier: LiteralExpression) {
|
||||
let node: Node = moduleSpecifier;
|
||||
while (node) {
|
||||
if (node.kind === SyntaxKind.ImportDeclaration) {
|
||||
return <ImportDeclaration>node;
|
||||
}
|
||||
if (node.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
return <ImportEqualsDeclaration>node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getUniqueSymbolId(symbol: Symbol) {
|
||||
if (symbol.flags & SymbolFlags.Alias) {
|
||||
return getSymbolId(checker.getAliasedSymbol(symbol));
|
||||
}
|
||||
return getSymbolId(symbol);
|
||||
}
|
||||
|
||||
function checkSymbolHasMeaning(symbol: Symbol, meaning: SemanticMeaning) {
|
||||
const declarations = symbol.getDeclarations();
|
||||
return declarations ? some(symbol.declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning)) : false;
|
||||
}
|
||||
|
||||
function getCodeActionForImport(moduleSymbol: Symbol, isDefault?: boolean): ImportCodeAction[] {
|
||||
const existingDeclarations = getImportDeclarations(moduleSymbol);
|
||||
if (existingDeclarations.length > 0) {
|
||||
// With an existing import statement, there are more than one actions the user can do.
|
||||
return getCodeActionsForExistingImport(existingDeclarations);
|
||||
}
|
||||
else {
|
||||
return [getCodeActionForNewImport()];
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getCodeActionsForExistingImport(declarations: (ImportDeclaration | ImportEqualsDeclaration)[]): ImportCodeAction[] {
|
||||
const actions: ImportCodeAction[] = [];
|
||||
|
||||
// It is possible that multiple import statements with the same specifier exist in the file.
|
||||
// e.g.
|
||||
//
|
||||
// import * as ns from "foo";
|
||||
// import { member1, member2 } from "foo";
|
||||
//
|
||||
// member3/**/ <-- cusor here
|
||||
//
|
||||
// in this case we should provie 2 actions:
|
||||
// 1. change "member3" to "ns.member3"
|
||||
// 2. add "member3" to the second import statement's import list
|
||||
// and it is up to the user to decide which one fits best.
|
||||
let namespaceImportDeclaration: ImportDeclaration | ImportEqualsDeclaration;
|
||||
let namedImportDeclaration: ImportDeclaration;
|
||||
let existingModuleSpecifier: string;
|
||||
for (const declaration of declarations) {
|
||||
if (declaration.kind === SyntaxKind.ImportDeclaration) {
|
||||
const namedBindings = declaration.importClause && declaration.importClause.namedBindings;
|
||||
if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) {
|
||||
// case:
|
||||
// import * as ns from "foo"
|
||||
namespaceImportDeclaration = declaration;
|
||||
}
|
||||
else {
|
||||
// cases:
|
||||
// import default from "foo"
|
||||
// import { bar } from "foo" or combination with the first one
|
||||
// import "foo"
|
||||
namedImportDeclaration = declaration;
|
||||
}
|
||||
existingModuleSpecifier = declaration.moduleSpecifier.getText();
|
||||
}
|
||||
else {
|
||||
// case:
|
||||
// import foo = require("foo")
|
||||
namespaceImportDeclaration = declaration;
|
||||
existingModuleSpecifier = getModuleSpecifierFromImportEqualsDeclaration(declaration);
|
||||
}
|
||||
}
|
||||
|
||||
if (namespaceImportDeclaration) {
|
||||
actions.push(getCodeActionForNamespaceImport(namespaceImportDeclaration));
|
||||
}
|
||||
|
||||
if (namedImportDeclaration && namedImportDeclaration.importClause &&
|
||||
(namedImportDeclaration.importClause.name || namedImportDeclaration.importClause.namedBindings)) {
|
||||
/**
|
||||
* If the existing import declaration already has a named import list, just
|
||||
* insert the identifier into that list.
|
||||
*/
|
||||
const textChange = getTextChangeForImportClause(namedImportDeclaration.importClause);
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(namedImportDeclaration.moduleSpecifier.getText());
|
||||
actions.push(createCodeAction(
|
||||
Diagnostics.Add_0_to_existing_import_declaration_from_1,
|
||||
[name, moduleSpecifierWithoutQuotes],
|
||||
textChange.newText,
|
||||
textChange.span,
|
||||
sourceFile.fileName,
|
||||
"InsertingIntoExistingImport",
|
||||
moduleSpecifierWithoutQuotes
|
||||
));
|
||||
}
|
||||
else {
|
||||
// we need to create a new import statement, but the existing module specifier can be reused.
|
||||
actions.push(getCodeActionForNewImport(existingModuleSpecifier));
|
||||
}
|
||||
return actions;
|
||||
|
||||
function getModuleSpecifierFromImportEqualsDeclaration(declaration: ImportEqualsDeclaration) {
|
||||
if (declaration.moduleReference && declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference) {
|
||||
return declaration.moduleReference.expression.getText();
|
||||
}
|
||||
return declaration.moduleReference.getText();
|
||||
}
|
||||
|
||||
function getTextChangeForImportClause(importClause: ImportClause): TextChange {
|
||||
const newImportText = isDefault ? `default as ${name}` : name;
|
||||
const importList = <NamedImports>importClause.namedBindings;
|
||||
// case 1:
|
||||
// original text: import default from "module"
|
||||
// change to: import default, { name } from "module"
|
||||
if (!importList && importClause.name) {
|
||||
const start = importClause.name.getEnd();
|
||||
return {
|
||||
newText: `, { ${newImportText} }`,
|
||||
span: { start, length: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
// case 2:
|
||||
// original text: import {} from "module"
|
||||
// change to: import { name } from "module"
|
||||
if (importList.elements.length === 0) {
|
||||
const start = importList.getStart();
|
||||
return {
|
||||
newText: `{ ${newImportText} }`,
|
||||
span: { start, length: importList.getEnd() - start }
|
||||
};
|
||||
}
|
||||
|
||||
// case 3:
|
||||
// original text: import { foo, bar } from "module"
|
||||
// change to: import { foo, bar, name } from "module"
|
||||
const insertPoint = importList.elements[importList.elements.length - 1].getEnd();
|
||||
/**
|
||||
* If the import list has one import per line, preserve that. Otherwise, insert on same line as last element
|
||||
* import {
|
||||
* foo
|
||||
* } from "./module";
|
||||
*/
|
||||
const startLine = getLineOfLocalPosition(sourceFile, importList.getStart());
|
||||
const endLine = getLineOfLocalPosition(sourceFile, importList.getEnd());
|
||||
const oneImportPerLine = endLine - startLine > importList.elements.length;
|
||||
|
||||
return {
|
||||
newText: `,${oneImportPerLine ? context.newLineCharacter : " "}${newImportText}`,
|
||||
span: { start: insertPoint, length: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
function getCodeActionForNamespaceImport(declaration: ImportDeclaration | ImportEqualsDeclaration): ImportCodeAction {
|
||||
let namespacePrefix: string;
|
||||
if (declaration.kind === SyntaxKind.ImportDeclaration) {
|
||||
namespacePrefix = (<NamespaceImport>declaration.importClause.namedBindings).name.getText();
|
||||
}
|
||||
else {
|
||||
namespacePrefix = declaration.name.getText();
|
||||
}
|
||||
namespacePrefix = stripQuotes(namespacePrefix);
|
||||
|
||||
/**
|
||||
* Cases:
|
||||
* import * as ns from "mod"
|
||||
* import default, * as ns from "mod"
|
||||
* import ns = require("mod")
|
||||
*
|
||||
* Because there is no import list, we alter the reference to include the
|
||||
* namespace instead of altering the import declaration. For example, "foo" would
|
||||
* become "ns.foo"
|
||||
*/
|
||||
return createCodeAction(
|
||||
Diagnostics.Change_0_to_1,
|
||||
[name, `${namespacePrefix}.${name}`],
|
||||
`${namespacePrefix}.`,
|
||||
{ start: token.getStart(), length: 0 },
|
||||
sourceFile.fileName,
|
||||
"CodeChange"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getCodeActionForNewImport(moduleSpecifier?: string): ImportCodeAction {
|
||||
if (!cachedNewImportInsertPosition) {
|
||||
// insert after any existing imports
|
||||
let lastModuleSpecifierEnd = -1;
|
||||
for (const moduleSpecifier of sourceFile.imports) {
|
||||
const end = moduleSpecifier.getEnd();
|
||||
if (!lastModuleSpecifierEnd || end > lastModuleSpecifierEnd) {
|
||||
lastModuleSpecifierEnd = end;
|
||||
}
|
||||
}
|
||||
cachedNewImportInsertPosition = lastModuleSpecifierEnd > 0 ? sourceFile.getLineEndOfPosition(lastModuleSpecifierEnd) : sourceFile.getStart();
|
||||
}
|
||||
|
||||
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier || getModuleSpecifierForNewImport());
|
||||
const importStatementText = isDefault
|
||||
? `import ${name} from "${moduleSpecifierWithoutQuotes}"`
|
||||
: `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`;
|
||||
|
||||
// if this file doesn't have any import statements, insert an import statement and then insert a new line
|
||||
// between the only import statement and user code. Otherwise just insert the statement because chances
|
||||
// are there are already a new line seperating code and import statements.
|
||||
const newText = cachedNewImportInsertPosition === sourceFile.getStart()
|
||||
? `${importStatementText};${context.newLineCharacter}${context.newLineCharacter}`
|
||||
: `${context.newLineCharacter}${importStatementText};`;
|
||||
|
||||
return createCodeAction(
|
||||
Diagnostics.Import_0_from_1,
|
||||
[name, `"${moduleSpecifierWithoutQuotes}"`],
|
||||
newText,
|
||||
{ start: cachedNewImportInsertPosition, length: 0 },
|
||||
sourceFile.fileName,
|
||||
"NewImport",
|
||||
moduleSpecifierWithoutQuotes
|
||||
);
|
||||
|
||||
function getModuleSpecifierForNewImport() {
|
||||
const fileName = sourceFile.path;
|
||||
const moduleFileName = moduleSymbol.valueDeclaration.getSourceFile().path;
|
||||
const sourceDirectory = getDirectoryPath(fileName);
|
||||
const options = context.program.getCompilerOptions();
|
||||
|
||||
return tryGetModuleNameFromAmbientModule() ||
|
||||
tryGetModuleNameFromBaseUrl() ||
|
||||
tryGetModuleNameFromRootDirs() ||
|
||||
tryGetModuleNameFromTypeRoots() ||
|
||||
tryGetModuleNameAsNodeModule() ||
|
||||
removeFileExtension(getRelativePath(moduleFileName, sourceDirectory));
|
||||
|
||||
function tryGetModuleNameFromAmbientModule(): string {
|
||||
if (moduleSymbol.valueDeclaration.kind !== SyntaxKind.SourceFile) {
|
||||
return moduleSymbol.name;
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetModuleNameFromBaseUrl() {
|
||||
if (!options.baseUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalizedBaseUrl = toPath(options.baseUrl, getDirectoryPath(options.baseUrl), getCanonicalFileName);
|
||||
let relativeName = tryRemoveParentDirectoryName(moduleFileName, normalizedBaseUrl);
|
||||
if (!relativeName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
relativeName = removeExtensionAndIndexPostFix(relativeName);
|
||||
|
||||
if (options.paths) {
|
||||
for (const key in options.paths) {
|
||||
for (const pattern of options.paths[key]) {
|
||||
const indexOfStar = pattern.indexOf("*");
|
||||
if (indexOfStar === 0 && pattern.length === 1) {
|
||||
continue;
|
||||
}
|
||||
else if (indexOfStar !== -1) {
|
||||
const prefix = pattern.substr(0, indexOfStar);
|
||||
const suffix = pattern.substr(indexOfStar + 1);
|
||||
if (relativeName.length >= prefix.length + suffix.length &&
|
||||
startsWith(relativeName, prefix) &&
|
||||
endsWith(relativeName, suffix)) {
|
||||
const matchedStar = relativeName.substr(prefix.length, relativeName.length - suffix.length);
|
||||
return key.replace("\*", matchedStar);
|
||||
}
|
||||
}
|
||||
else if (pattern === relativeName) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return relativeName;
|
||||
}
|
||||
|
||||
function tryGetModuleNameFromRootDirs() {
|
||||
if (options.rootDirs) {
|
||||
const normalizedRootDirs = map(options.rootDirs, rootDir => toPath(rootDir, /*basePath*/ undefined, getCanonicalFileName));
|
||||
const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, normalizedRootDirs);
|
||||
const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, normalizedRootDirs);
|
||||
if (normalizedTargetPath !== undefined) {
|
||||
const relativePath = normalizedSourcePath !== undefined ? getRelativePath(normalizedTargetPath, normalizedSourcePath) : normalizedTargetPath;
|
||||
return removeFileExtension(relativePath);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function tryGetModuleNameFromTypeRoots() {
|
||||
const typeRoots = getEffectiveTypeRoots(options, context.host);
|
||||
if (typeRoots) {
|
||||
const normalizedTypeRoots = map(typeRoots, typeRoot => toPath(typeRoot, /*basePath*/ undefined, getCanonicalFileName));
|
||||
for (const typeRoot of normalizedTypeRoots) {
|
||||
if (startsWith(moduleFileName, typeRoot)) {
|
||||
let relativeFileName = moduleFileName.substring(typeRoot.length + 1);
|
||||
return removeExtensionAndIndexPostFix(relativeFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tryGetModuleNameAsNodeModule() {
|
||||
if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) {
|
||||
// nothing to do here
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const indexOfNodeModules = moduleFileName.indexOf("node_modules");
|
||||
if (indexOfNodeModules < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let relativeFileName: string;
|
||||
if (sourceDirectory.indexOf(moduleFileName.substring(0, indexOfNodeModules - 1)) === 0) {
|
||||
// if node_modules folder is in this folder or any of its parent folder, no need to keep it.
|
||||
relativeFileName = moduleFileName.substring(indexOfNodeModules + 13 /* "node_modules\".length */);
|
||||
}
|
||||
else {
|
||||
relativeFileName = getRelativePath(moduleFileName, sourceDirectory);
|
||||
}
|
||||
|
||||
relativeFileName = removeFileExtension(relativeFileName);
|
||||
if (endsWith(relativeFileName, "/index")) {
|
||||
relativeFileName = getDirectoryPath(relativeFileName);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const moduleDirectory = getDirectoryPath(moduleFileName);
|
||||
const packageJsonContent = JSON.parse(context.host.readFile(combinePaths(moduleDirectory, "package.json")));
|
||||
if (packageJsonContent) {
|
||||
const mainFile = packageJsonContent.main || packageJsonContent.typings;
|
||||
if (mainFile) {
|
||||
const mainExportFile = toPath(mainFile, moduleDirectory, getCanonicalFileName);
|
||||
if (removeFileExtension(mainExportFile) === removeFileExtension(moduleFileName)) {
|
||||
relativeFileName = getDirectoryPath(relativeFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
|
||||
return relativeFileName;
|
||||
}
|
||||
}
|
||||
|
||||
function getPathRelativeToRootDirs(path: Path, rootDirs: Path[]) {
|
||||
for (const rootDir of rootDirs) {
|
||||
const relativeName = tryRemoveParentDirectoryName(path, rootDir);
|
||||
if (relativeName !== undefined) {
|
||||
return relativeName;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function removeExtensionAndIndexPostFix(fileName: string) {
|
||||
fileName = removeFileExtension(fileName);
|
||||
if (endsWith(fileName, "/index")) {
|
||||
fileName = fileName.substr(0, fileName.length - 6/* "/index".length */);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
function getRelativePath(path: string, directoryPath: string) {
|
||||
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, false);
|
||||
return moduleHasNonRelativeName(relativePath) ? "./" + relativePath : relativePath;
|
||||
}
|
||||
|
||||
function tryRemoveParentDirectoryName(path: Path, parentDirectory: Path) {
|
||||
const index = path.indexOf(parentDirectory);
|
||||
if (index === 0) {
|
||||
return endsWith(parentDirectory, directorySeparator)
|
||||
? path.substring(parentDirectory.length)
|
||||
: path.substring(parentDirectory.length + 1);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createCodeAction(
|
||||
description: DiagnosticMessage,
|
||||
diagnosticArgs: string[],
|
||||
newText: string,
|
||||
span: TextSpan,
|
||||
fileName: string,
|
||||
kind: ImportCodeActionKind,
|
||||
moduleSpecifier?: string): ImportCodeAction {
|
||||
return {
|
||||
description: formatMessage.apply(undefined, [undefined, description].concat(<any[]>diagnosticArgs)),
|
||||
changes: [{ fileName, textChanges: [{ newText, span }] }],
|
||||
kind,
|
||||
moduleSpecifier
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path="..\compiler\program.ts"/>
|
||||
/// <reference path="..\compiler\program.ts"/>
|
||||
/// <reference path="..\compiler\commandLineParser.ts"/>
|
||||
|
||||
/// <reference path='types.ts' />
|
||||
|
@ -492,6 +492,23 @@ namespace ts {
|
|||
return ts.getPositionOfLineAndCharacter(this, line, character);
|
||||
}
|
||||
|
||||
public getLineEndOfPosition(pos: number): number {
|
||||
const { line } = this.getLineAndCharacterOfPosition(pos);
|
||||
const lineStarts = this.getLineStarts();
|
||||
|
||||
let lastCharPos: number;
|
||||
if (line + 1 >= lineStarts.length) {
|
||||
lastCharPos = this.getEnd();
|
||||
}
|
||||
if (!lastCharPos) {
|
||||
lastCharPos = lineStarts[line + 1] - 1;
|
||||
}
|
||||
|
||||
const fullText = this.getFullText();
|
||||
// if the new line is "\r\n", we should return the last non-new-line-character position
|
||||
return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos;
|
||||
}
|
||||
|
||||
public getNamedDeclarations(): Map<Declaration[]> {
|
||||
if (!this.namedDeclarations) {
|
||||
this.namedDeclarations = this.computeNamedDeclarations();
|
||||
|
@ -1676,7 +1693,9 @@ namespace ts {
|
|||
sourceFile: sourceFile,
|
||||
span: span,
|
||||
program: program,
|
||||
newLineCharacter: newLineChar
|
||||
newLineCharacter: newLineChar,
|
||||
host: host,
|
||||
cancellationToken: cancellationToken
|
||||
};
|
||||
|
||||
const fixes = codefix.getFixes(context);
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace ts {
|
|||
/* @internal */ getNamedDeclarations(): Map<Declaration[]>;
|
||||
|
||||
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
|
||||
getLineEndOfPosition(pos: number): number;
|
||||
getLineStarts(): number[];
|
||||
getPositionOfLineAndCharacter(line: number, character: number): number;
|
||||
update(newText: string, textChangeRange: TextChangeRange): SourceFile;
|
||||
|
|
|
@ -211,6 +211,7 @@ declare namespace FourSlashInterface {
|
|||
DocCommentTemplate(expectedText: string, expectedOffset: number, empty?: boolean): void;
|
||||
noDocCommentTemplate(): void;
|
||||
codeFixAtPosition(expectedText: string, errorCode?: number): void;
|
||||
importFixAtPosition(expectedTextArray: string[], errorCode?: number): void;
|
||||
|
||||
navigationBar(json: any): void;
|
||||
navigationTree(json: any): void;
|
||||
|
|
10
tests/cases/fourslash/importNameCodeFixExistingImport0.ts
Normal file
10
tests/cases/fourslash/importNameCodeFixExistingImport0.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{ v1 }|] from "./module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
|
||||
verify.importFixAtPosition([`{ v1, f1 }`]);
|
11
tests/cases/fourslash/importNameCodeFixExistingImport1.ts
Normal file
11
tests/cases/fourslash/importNameCodeFixExistingImport1.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import d, [|{ v1 }|] from "./module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export default var d1 = 6;
|
||||
|
||||
verify.importFixAtPosition([`{ v1, f1 }`]);
|
21
tests/cases/fourslash/importNameCodeFixExistingImport10.ts
Normal file
21
tests/cases/fourslash/importNameCodeFixExistingImport10.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{
|
||||
//// v1,
|
||||
//// v2
|
||||
//// }|] from "./module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export var v2 = 5;
|
||||
//// export var v3 = 5;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`{
|
||||
v1,
|
||||
v2,
|
||||
f1
|
||||
}`
|
||||
]);
|
20
tests/cases/fourslash/importNameCodeFixExistingImport11.ts
Normal file
20
tests/cases/fourslash/importNameCodeFixExistingImport11.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////import [|{
|
||||
//// v1, v2,
|
||||
//// v3
|
||||
////}|] from "./module";
|
||||
////f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export var v2 = 5;
|
||||
//// export var v3 = 5;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`{
|
||||
v1, v2,
|
||||
v3, f1
|
||||
}`
|
||||
]);
|
12
tests/cases/fourslash/importNameCodeFixExistingImport12.ts
Normal file
12
tests/cases/fourslash/importNameCodeFixExistingImport12.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{}|] from "./module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export var v2 = 5;
|
||||
//// export var v3 = 5;
|
||||
|
||||
verify.importFixAtPosition([`{ f1 }`]);
|
16
tests/cases/fourslash/importNameCodeFixExistingImport2.ts
Normal file
16
tests/cases/fourslash/importNameCodeFixExistingImport2.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import * as ns from "./module";
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import * as ns from "./module";
|
||||
import { f1 } from "./module";
|
||||
f1();`,
|
||||
`import * as ns from "./module";
|
||||
ns.f1();`
|
||||
]);
|
18
tests/cases/fourslash/importNameCodeFixExistingImport3.ts
Normal file
18
tests/cases/fourslash/importNameCodeFixExistingImport3.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import d, * as ns from "./module" ;
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export default var d1 = 6;
|
||||
|
||||
// Test with some extra spaces before the semicolon
|
||||
verify.importFixAtPosition([
|
||||
`import d, * as ns from "./module" ;
|
||||
ns.f1();`,
|
||||
`import d, * as ns from "./module" ;
|
||||
import { f1 } from "./module";
|
||||
f1();`,
|
||||
]);
|
14
tests/cases/fourslash/importNameCodeFixExistingImport4.ts
Normal file
14
tests/cases/fourslash/importNameCodeFixExistingImport4.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import d from "./module";
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export default var d1 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import d, { f1 } from "./module";
|
||||
f1();`
|
||||
]);
|
12
tests/cases/fourslash/importNameCodeFixExistingImport5.ts
Normal file
12
tests/cases/fourslash/importNameCodeFixExistingImport5.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import "./module";
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
|
||||
verify.importFixAtPosition([`import "./module";
|
||||
import { f1 } from "./module";
|
||||
f1();`]);
|
13
tests/cases/fourslash/importNameCodeFixExistingImport6.ts
Normal file
13
tests/cases/fourslash/importNameCodeFixExistingImport6.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{ v1 }|] from "fake-module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: ../package.json
|
||||
//// { "dependencies": { "fake-module": "latest" } }
|
||||
|
||||
// @Filename: ../node_modules/fake-module/index.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
verify.importFixAtPosition([`{ v1, f1 }`]);
|
10
tests/cases/fourslash/importNameCodeFixExistingImport7.ts
Normal file
10
tests/cases/fourslash/importNameCodeFixExistingImport7.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{ v1 }|] from "../other_dir/module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: ../other_dir/module.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
verify.importFixAtPosition([`{ v1, f1 }`]);
|
12
tests/cases/fourslash/importNameCodeFixExistingImport8.ts
Normal file
12
tests/cases/fourslash/importNameCodeFixExistingImport8.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{v1, v2, v3,}|] from "./module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
//// export var v2 = 5;
|
||||
//// export var v3 = 5;
|
||||
|
||||
verify.importFixAtPosition([`{v1, v2, v3, f1,}`]);
|
17
tests/cases/fourslash/importNameCodeFixExistingImport9.ts
Normal file
17
tests/cases/fourslash/importNameCodeFixExistingImport9.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import [|{
|
||||
//// v1
|
||||
//// }|] from "./module";
|
||||
//// f1/*0*/();
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`{
|
||||
v1,
|
||||
f1
|
||||
}`
|
||||
]);
|
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import ns = require("ambient-module");
|
||||
//// var x = v1/*0*/ + 5;|]
|
||||
|
||||
// @Filename: ambientModule.ts
|
||||
//// declare module "ambient-module" {
|
||||
//// export function f1();
|
||||
//// export var v1;
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import ns = require("ambient-module");
|
||||
var x = ns.v1 + 5;`,
|
||||
`import ns = require("ambient-module");
|
||||
import { v1 } from "ambient-module";
|
||||
var x = v1 + 5;`,
|
||||
]);
|
15
tests/cases/fourslash/importNameCodeFixNewImportAmbient0.ts
Normal file
15
tests/cases/fourslash/importNameCodeFixNewImportAmbient0.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: ambientModule.ts
|
||||
//// declare module "ambient-module" {
|
||||
//// export function f1();
|
||||
//// export var v1;
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "ambient-module";
|
||||
|
||||
f1();`
|
||||
]);
|
28
tests/cases/fourslash/importNameCodeFixNewImportAmbient1.ts
Normal file
28
tests/cases/fourslash/importNameCodeFixNewImportAmbient1.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// import d from "other-ambient-module";
|
||||
//// [|import * as ns from "yet-another-ambient-module";
|
||||
//// var x = v1/*0*/ + 5;|]
|
||||
|
||||
// @Filename: ambientModule.ts
|
||||
//// declare module "ambient-module" {
|
||||
//// export function f1();
|
||||
//// export var v1;
|
||||
//// }
|
||||
|
||||
// @Filename: otherAmbientModule.ts
|
||||
//// declare module "other-ambient-module" {
|
||||
//// export default function f2();
|
||||
//// }
|
||||
|
||||
// @Filename: yetAnotherAmbientModule.ts
|
||||
//// declare module "yet-another-ambient-module" {
|
||||
//// export function f3();
|
||||
//// export var v3;
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import * as ns from "yet-another-ambient-module";
|
||||
import { v1 } from "ambient-module";
|
||||
var x = v1 + 5;`
|
||||
]);
|
21
tests/cases/fourslash/importNameCodeFixNewImportAmbient2.ts
Normal file
21
tests/cases/fourslash/importNameCodeFixNewImportAmbient2.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////[|/*
|
||||
//// * I'm a license or something
|
||||
//// */
|
||||
////f1/*0*/();|]
|
||||
|
||||
// @Filename: ambientModule.ts
|
||||
//// declare module "ambient-module" {
|
||||
//// export function f1();
|
||||
//// export var v1;
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`/*
|
||||
* I'm a license or something
|
||||
*/
|
||||
import { f1 } from "ambient-module";
|
||||
|
||||
f1();`
|
||||
]);
|
30
tests/cases/fourslash/importNameCodeFixNewImportAmbient3.ts
Normal file
30
tests/cases/fourslash/importNameCodeFixNewImportAmbient3.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// let a = "I am a non-trivial statement that appears before imports";
|
||||
//// import d from "other-ambient-module"
|
||||
//// [|import * as ns from "yet-another-ambient-module"
|
||||
//// var x = v1/*0*/ + 5;|]
|
||||
|
||||
// @Filename: ambientModule.ts
|
||||
//// declare module "ambient-module" {
|
||||
//// export function f1();
|
||||
//// export var v1;
|
||||
//// }
|
||||
|
||||
// @Filename: otherAmbientModule.ts
|
||||
//// declare module "other-ambient-module" {
|
||||
//// export default function f2();
|
||||
//// }
|
||||
|
||||
// @Filename: yetAnotherAmbientModule.ts
|
||||
//// declare module "yet-another-ambient-module" {
|
||||
//// export function f3();
|
||||
//// export var v3;
|
||||
//// }
|
||||
|
||||
// test cases when there are no semicolons at the line end
|
||||
verify.importFixAtPosition([
|
||||
`import * as ns from "yet-another-ambient-module"
|
||||
import { v1 } from "ambient-module";
|
||||
var x = v1 + 5;`
|
||||
]);
|
19
tests/cases/fourslash/importNameCodeFixNewImportBaseUrl0.ts
Normal file
19
tests/cases/fourslash/importNameCodeFixNewImportBaseUrl0.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "baseUrl": "./a"
|
||||
//// }
|
||||
//// }
|
||||
|
||||
// @Filename: a/b.ts
|
||||
//// export function f1() { };
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "b";
|
||||
|
||||
f1();`
|
||||
]);
|
12
tests/cases/fourslash/importNameCodeFixNewImportDefault0.ts
Normal file
12
tests/cases/fourslash/importNameCodeFixNewImportDefault0.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export default function f1() { };
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import f1 from "./module";
|
||||
|
||||
f1();`
|
||||
]);
|
13
tests/cases/fourslash/importNameCodeFixNewImportFile0.ts
Normal file
13
tests/cases/fourslash/importNameCodeFixNewImportFile0.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "./module";
|
||||
|
||||
f1();`
|
||||
]);
|
18
tests/cases/fourslash/importNameCodeFixNewImportFile1.ts
Normal file
18
tests/cases/fourslash/importNameCodeFixNewImportFile1.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|/// <reference path="./tripleSlashReference.ts" />
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module.ts
|
||||
//// export function f1() {}
|
||||
//// export var v1 = 5;
|
||||
|
||||
// @Filename: tripleSlashReference.ts
|
||||
//// var x = 5;/*dummy*/
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`/// <reference path="./tripleSlashReference.ts" />
|
||||
import { f1 } from "./module";
|
||||
|
||||
f1();`
|
||||
]);
|
13
tests/cases/fourslash/importNameCodeFixNewImportFile2.ts
Normal file
13
tests/cases/fourslash/importNameCodeFixNewImportFile2.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: ../../other_dir/module.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "../../other_dir/module";
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,19 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: ../package.json
|
||||
//// { "dependencies": { "fake-module": "latest" } }
|
||||
|
||||
// @Filename: ../node_modules/fake-module/index.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
// @Filename: ../node_modules/fake-module/package.json
|
||||
//// {}
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "fake-module";
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,16 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: ../package.json
|
||||
//// { "dependencies": { "fake-module": "latest" } }
|
||||
|
||||
// @Filename: ../node_modules/fake-module/nested.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "fake-module/nested";
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,25 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: ../package.json
|
||||
//// { "dependencies": { "fake-module": "latest" } }
|
||||
|
||||
// @Filename: ../node_modules/fake-module/notindex.d.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
// @Filename: ../node_modules/fake-module/notindex.js
|
||||
//// module.exports = {
|
||||
//// v1: 5,
|
||||
//// f1: function () {}
|
||||
//// };
|
||||
|
||||
// @Filename: ../node_modules/fake-module/package.json
|
||||
//// { "main":"./notindex.js", "typings":"./notindex.d.ts" }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "fake-module";
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,14 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
//// [|f1/*0*/();|]
|
||||
|
||||
// @Filename: /node_modules/@types/random/index.d.ts
|
||||
//// export var v1 = 5;
|
||||
//// export function f1();
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from "random";
|
||||
|
||||
f1();`
|
||||
]);
|
22
tests/cases/fourslash/importNameCodeFixNewImportPaths0.ts
Normal file
22
tests/cases/fourslash/importNameCodeFixNewImportPaths0.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|foo/*0*/();|]
|
||||
|
||||
// @Filename: folder_a/f2.ts
|
||||
//// export function foo() {};
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "baseUrl": ".",
|
||||
//// "paths": {
|
||||
//// "a": [ "folder_a/f2" ]
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { foo } from "a";
|
||||
|
||||
foo();`
|
||||
]);
|
22
tests/cases/fourslash/importNameCodeFixNewImportPaths1.ts
Normal file
22
tests/cases/fourslash/importNameCodeFixNewImportPaths1.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|foo/*0*/();|]
|
||||
|
||||
// @Filename: folder_b/f2.ts
|
||||
//// export function foo() {};
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "baseUrl": ".",
|
||||
//// "paths": {
|
||||
//// "b/*": [ "folder_b/*" ]
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { foo } from "b/f2";
|
||||
|
||||
foo();`
|
||||
]);
|
23
tests/cases/fourslash/importNameCodeFixNewImportRootDirs0.ts
Normal file
23
tests/cases/fourslash/importNameCodeFixNewImportRootDirs0.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a/f1.ts
|
||||
//// [|foo/*0*/();|]
|
||||
|
||||
// @Filename: b/c/f2.ts
|
||||
//// export function foo() {};
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "rootDirs": [
|
||||
//// "a",
|
||||
//// "b/c"
|
||||
//// ]
|
||||
//// }
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { foo } from "./f2";
|
||||
|
||||
foo();`
|
||||
]);
|
|
@ -0,0 +1,22 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a/f1.ts
|
||||
//// [|foo/*0*/();|]
|
||||
|
||||
// @Filename: types/random/index.ts
|
||||
//// export function foo() {};
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "typeRoots": [
|
||||
//// "./types"
|
||||
//// ]
|
||||
//// }
|
||||
//// }
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { foo } from "random";
|
||||
|
||||
foo();`
|
||||
]);
|
20
tests/cases/fourslash/importNameCodeFixOptionalImport0.ts
Normal file
20
tests/cases/fourslash/importNameCodeFixOptionalImport0.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a/f1.ts
|
||||
//// [|import * as ns from "./foo";
|
||||
//// foo/*0*/();|]
|
||||
|
||||
// @Filename: a/foo/bar.ts
|
||||
//// export function foo() {};
|
||||
|
||||
// @Filename: a/foo.ts
|
||||
//// export { foo } from "./foo/bar";
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import * as ns from "./foo";
|
||||
import { foo } from "./foo";
|
||||
foo();`,
|
||||
|
||||
`import * as ns from "./foo";
|
||||
ns.foo();`,
|
||||
]);
|
20
tests/cases/fourslash/importNameCodeFixOptionalImport1.ts
Normal file
20
tests/cases/fourslash/importNameCodeFixOptionalImport1.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a/f1.ts
|
||||
//// [|foo/*0*/();|]
|
||||
|
||||
// @Filename: a/node_modules/bar/index.ts
|
||||
//// export function foo() {};
|
||||
|
||||
// @Filename: a/foo.ts
|
||||
//// export { foo } from "bar";
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { foo } from "./foo";
|
||||
|
||||
foo();`,
|
||||
|
||||
`import { foo } from "bar";
|
||||
|
||||
foo();`,
|
||||
]);
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
/// <reference path='../fourslash.ts' />
|
||||
|
||||
////class Base{
|
||||
////}
|
||||
|
|
Loading…
Reference in a new issue