Merge pull request #17750 from Microsoft/importFixInferQuote
Add support to infer the quote style for import code fix
This commit is contained in:
commit
95ee9aef0f
9 changed files with 163 additions and 11 deletions
|
@ -999,6 +999,7 @@ namespace ts {
|
|||
export interface StringLiteral extends LiteralExpression {
|
||||
kind: SyntaxKind.StringLiteral;
|
||||
/* @internal */ textSourceNode?: Identifier | StringLiteral | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
|
||||
/* @internal */ singleQuote?: boolean;
|
||||
}
|
||||
|
||||
// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing.
|
||||
|
|
|
@ -344,15 +344,20 @@ namespace ts {
|
|||
// or a (possibly escaped) quoted form of the original text if it's string-like.
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.StringLiteral:
|
||||
return '"' + escapeText(node.text) + '"';
|
||||
if ((<StringLiteral>node).singleQuote) {
|
||||
return "'" + escapeText(node.text, CharacterCodes.singleQuote) + "'";
|
||||
}
|
||||
else {
|
||||
return '"' + escapeText(node.text, CharacterCodes.doubleQuote) + '"';
|
||||
}
|
||||
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
return "`" + escapeText(node.text) + "`";
|
||||
return "`" + escapeText(node.text, CharacterCodes.backtick) + "`";
|
||||
case SyntaxKind.TemplateHead:
|
||||
return "`" + escapeText(node.text) + "${";
|
||||
return "`" + escapeText(node.text, CharacterCodes.backtick) + "${";
|
||||
case SyntaxKind.TemplateMiddle:
|
||||
return "}" + escapeText(node.text) + "${";
|
||||
return "}" + escapeText(node.text, CharacterCodes.backtick) + "${";
|
||||
case SyntaxKind.TemplateTail:
|
||||
return "}" + escapeText(node.text) + "`";
|
||||
return "}" + escapeText(node.text, CharacterCodes.backtick) + "`";
|
||||
case SyntaxKind.NumericLiteral:
|
||||
return node.text;
|
||||
}
|
||||
|
@ -2356,7 +2361,9 @@ namespace ts {
|
|||
// the language service. These characters should be escaped when printing, and if any characters are added,
|
||||
// the map below must be updated. Note that this regexp *does not* include the 'delete' character.
|
||||
// There is no reason for this other than that JSON.stringify does not handle it either.
|
||||
const escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
|
||||
const doubleQuoteEscapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
|
||||
const singleQuoteEscapedCharsRegExp = /[\\\'\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
|
||||
const backtickQuoteEscapedCharsRegExp = /[\\\`\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g;
|
||||
const escapedCharsMap = createMapFromTemplate({
|
||||
"\0": "\\0",
|
||||
"\t": "\\t",
|
||||
|
@ -2367,18 +2374,23 @@ namespace ts {
|
|||
"\n": "\\n",
|
||||
"\\": "\\\\",
|
||||
"\"": "\\\"",
|
||||
"\'": "\\\'",
|
||||
"\`": "\\\`",
|
||||
"\u2028": "\\u2028", // lineSeparator
|
||||
"\u2029": "\\u2029", // paragraphSeparator
|
||||
"\u0085": "\\u0085" // nextLine
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2),
|
||||
* but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine)
|
||||
* Note that this doesn't actually wrap the input in double quotes.
|
||||
*/
|
||||
export function escapeString(s: string): string {
|
||||
export function escapeString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string {
|
||||
const escapedCharsRegExp =
|
||||
quoteChar === CharacterCodes.backtick ? backtickQuoteEscapedCharsRegExp :
|
||||
quoteChar === CharacterCodes.singleQuote ? singleQuoteEscapedCharsRegExp :
|
||||
doubleQuoteEscapedCharsRegExp;
|
||||
return s.replace(escapedCharsRegExp, getReplacement);
|
||||
}
|
||||
|
||||
|
@ -2400,8 +2412,8 @@ namespace ts {
|
|||
}
|
||||
|
||||
const nonAsciiCharacters = /[^\u0000-\u007F]/g;
|
||||
export function escapeNonAsciiString(s: string): string {
|
||||
s = escapeString(s);
|
||||
export function escapeNonAsciiString(s: string, quoteChar?: CharacterCodes.doubleQuote | CharacterCodes.singleQuote | CharacterCodes.backtick): string {
|
||||
s = escapeString(s, quoteChar);
|
||||
// Replace non-ASCII characters with '\uNNNN' escapes if any exist.
|
||||
// Otherwise just return the original string.
|
||||
return nonAsciiCharacters.test(s) ?
|
||||
|
|
|
@ -394,7 +394,9 @@ namespace ts.codefix {
|
|||
: isNamespaceImport
|
||||
? createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(symbolName)))
|
||||
: createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, createIdentifier(symbolName))]));
|
||||
const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifierWithoutQuotes));
|
||||
const moduleSpecifierLiteral = createLiteral(moduleSpecifierWithoutQuotes);
|
||||
moduleSpecifierLiteral.singleQuote = getSingleQuoteStyleFromExistingImports();
|
||||
const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, moduleSpecifierLiteral);
|
||||
if (!lastImportDeclaration) {
|
||||
changeTracker.insertNodeAt(sourceFile, getSourceFileImportLocation(sourceFile), importDecl, { suffix: `${context.newLineCharacter}${context.newLineCharacter}` });
|
||||
}
|
||||
|
@ -435,6 +437,24 @@ namespace ts.codefix {
|
|||
return position;
|
||||
}
|
||||
|
||||
function getSingleQuoteStyleFromExistingImports() {
|
||||
const firstModuleSpecifier = forEach(sourceFile.statements, node => {
|
||||
if (isImportDeclaration(node) || isExportDeclaration(node)) {
|
||||
if (node.moduleSpecifier && isStringLiteral(node.moduleSpecifier)) {
|
||||
return node.moduleSpecifier;
|
||||
}
|
||||
}
|
||||
else if (isImportEqualsDeclaration(node)) {
|
||||
if (isExternalModuleReference(node.moduleReference) && isStringLiteral(node.moduleReference.expression)) {
|
||||
return node.moduleReference.expression;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (firstModuleSpecifier) {
|
||||
return sourceFile.text.charCodeAt(firstModuleSpecifier.getStart()) === CharacterCodes.singleQuote;
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleSpecifierForNewImport() {
|
||||
const fileName = sourceFile.fileName;
|
||||
const moduleFileName = moduleSymbol.valueDeclaration.getSourceFile().fileName;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import { v2 } from './module2';
|
||||
////
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module1.ts
|
||||
//// export function f1() {}
|
||||
|
||||
// @Filename: module2.ts
|
||||
//// export var v2 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { v2 } from './module2';
|
||||
import { f1 } from './module1';
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import { v2 } from "./module2";
|
||||
////
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module1.ts
|
||||
//// export function f1() {}
|
||||
|
||||
// @Filename: module2.ts
|
||||
//// export var v2 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { v2 } from "./module2";
|
||||
import { f1 } from "./module1";
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import m2 = require('./module2');
|
||||
////
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module1.ts
|
||||
//// export function f1() {}
|
||||
|
||||
// @Filename: module2.ts
|
||||
//// export var v2 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import m2 = require('./module2');
|
||||
import { f1 } from './module1';
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,19 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|export { v2 } from './module2';
|
||||
////
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module1.ts
|
||||
//// export function f1() {}
|
||||
|
||||
// @Filename: module2.ts
|
||||
//// export var v2 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { f1 } from './module1';
|
||||
|
||||
export { v2 } from './module2';
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,23 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import { v2 } from "./module2";
|
||||
//// import { v3 } from './module3';
|
||||
////
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module1.ts
|
||||
//// export function f1() {}
|
||||
|
||||
// @Filename: module2.ts
|
||||
//// export var v2 = 6;
|
||||
|
||||
// @Filename: module3.ts
|
||||
//// export var v3 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { v2 } from "./module2";
|
||||
import { v3 } from './module3';
|
||||
import { f1 } from "./module1";
|
||||
|
||||
f1();`
|
||||
]);
|
|
@ -0,0 +1,23 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// [|import { v2 } from './module2';
|
||||
//// import { v3 } from "./module3";
|
||||
////
|
||||
//// f1/*0*/();|]
|
||||
|
||||
// @Filename: module1.ts
|
||||
//// export function f1() {}
|
||||
|
||||
// @Filename: module2.ts
|
||||
//// export var v2 = 6;
|
||||
|
||||
// @Filename: module3.ts
|
||||
//// export var v3 = 6;
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import { v2 } from './module2';
|
||||
import { v3 } from "./module3";
|
||||
import { f1 } from './module1';
|
||||
|
||||
f1();`
|
||||
]);
|
Loading…
Reference in a new issue