Add support to infer the quote style for import quick fixes

This commit is contained in:
Ron Buckton
2017-08-11 14:26:25 -07:00
parent d352e3b03f
commit e3b6df64b3
4 changed files with 62 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,23 @@ namespace ts.codefix {
return position;
}
function getSingleQuoteStyleFromExistingImports() {
const firstModuleSpecifier = forEach(sourceFile.statements, node => {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return (<ImportDeclaration>node).moduleSpecifier;
case SyntaxKind.ImportEqualsDeclaration:
const moduleReference = (<ImportEqualsDeclaration>node).moduleReference;
return moduleReference.kind === SyntaxKind.ExternalModuleReference ? moduleReference.expression : undefined;
case SyntaxKind.ExportDeclaration:
return (<ExportDeclaration>node).moduleSpecifier;
}
});
if (firstModuleSpecifier && isStringLiteral(firstModuleSpecifier)) {
return sourceFile.text.charCodeAt(skipTrivia(sourceFile.text, firstModuleSpecifier.pos)) === CharacterCodes.singleQuote;
}
}
function getModuleSpecifierForNewImport() {
const fileName = sourceFile.fileName;
const moduleFileName = moduleSymbol.valueDeclaration.getSourceFile().fileName;

View File

@@ -0,0 +1,19 @@
/// <reference path="fourslash.ts" />
//// [|import { v2 } from './module2';
////
//// f1/*0*/();|]
// @Filename: module.ts
//// export function f1() {}
//// export var v1 = 5;
// @Filename: module2.ts
//// export var v2 = 6;
verify.importFixAtPosition([
`import { v2 } from './module2';
import { f1 } from './module';
f1();`
]);