Merge pull request #17750 from Microsoft/importFixInferQuote

Add support to infer the quote style for import code fix
This commit is contained in:
Ron Buckton 2017-08-11 15:52:38 -07:00 committed by GitHub
commit 95ee9aef0f
9 changed files with 163 additions and 11 deletions

View file

@ -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.

View file

@ -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) ?

View file

@ -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;

View file

@ -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();`
]);

View file

@ -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();`
]);

View file

@ -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();`
]);

View file

@ -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();`
]);

View file

@ -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();`
]);

View file

@ -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();`
]);