typescript2.0 - Byt citattecken med TypeScript-kompilatorns API-transformatorer


Jag har ett TypeScript-kodgenereringsscenario, där jag konstruerar ett AST och sedan skriver ut det och sparar det till en fil. Som standard omges de utskrivna strängliteralerna av dubbla citattecken, jag skulle vilja ha ett alternativ för att använda enkla citattecken. Som nämnts här bör jag kunna gå igenom trädet och ersätta strängliteralerna, men jag är osäker på hur.

export const quotemarkTransformer = <T extends ts.Node>(context: ts.TransformationContext) => (rootNode: T) => {
    function visit(node: ts.Node): ts.Node {
        node = ts.visitEachChild(node, visit, context);
        if (node.kind === ts.SyntaxKind.StringLiteral) {
            const stringLiteral = node as ts.StringLiteral;
            // change raw node text?
            return ts.createLiteral(stringLiteral.text);
        }
        return node;
    }
    return ts.visitNode(rootNode, visit);
}

Att skapa strängliteral med TS fabriksfunktioner ts.createLiteral(stringLiteral.text) kommer alltid att använda dubbla citattecken. Finns det något sätt att direkt komma åt och ändra det emitterade texten?

Svar #1

Det finns en intern egenskap som du kan ställa in på StringLiteral för att göra detta:

if (node.kind === ts.SyntaxKind.StringLiteral)
    (node as any).singleQuote = true;

Se här och här.

Det är mycket viktigt att notera att detta skulle bero på en egenskap som inte finns i det offentliga API:et, så det kan sluta fungera en dag. Om du känner dig obekväm med att göra detta, följ anvisningarna nedan.

Givet den utsända texten:

  1. Analysera eller återanvänd ett AST som matchar den emitterade texten.
  2. Traversera AST:en och för varje strängliteral som innehåller sökningen efter citattecknet, lagra startpositionen för båda citattecknen.
  3. Med den emitterade källfilens text och startpositionerna för citattecknen, ersätt texten vid varje position med det nya citattecknet.

Här är lite kod som visar ett exempel:

// setup
const emittedFilePath = "/file.js";
const emittedText = `'this'; 'is a'; "test";`;
const emittedSourceFile = ts.createSourceFile(
    emittedFilePath,
    emittedText,
    ts.ScriptTarget.Latest,
    false);

// replace all ' with "
console.log(replaceQuoteChars(emittedSourceFile, `'`, `"`));

// main code...
type QuoteChar = "'" | "\"";

function replaceQuoteChars<OldChar extends QuoteChar>(
    sourceFile: ts.SourceFile,
    oldChar: OldChar,
    newChar: Exclude<QuoteChar, OldChar>
) {
    return getNewText(
        getQuoteCharPositions(emittedSourceFile, oldChar)
    );

    function getNewText(quoteCharPositions: number[]) {
        const fileText = sourceFile.getFullText();
        let result = "";
        let lastPos = 0;

        for (const pos of quoteCharPositions) {
            result += fileText.substring(lastPos, pos) + newChar;
            lastPos = pos + 1;
        }

        result += fileText.substring(lastPos);
        return result;
    }
}

function getQuoteCharPositions(
    sourceFile: ts.SourceFile,
    searchingChar: QuoteChar
) {
    const sourceFileText = sourceFile.getFullText();
    const result: number[] = [];
    visitNode(sourceFile);
    return result;

    function visitNode(node: ts.Node) {
        if (ts.isStringLiteral(node))
            handleStringLiteral(node);
        else
            ts.forEachChild(node, visitNode);
    }

    function handleStringLiteral(node: ts.StringLiteral) {
        const start = node.getStart(sourceFile);
        const quoteChar = sourceFileText[start];

        if (quoteChar === searchingChar) {
            result.push(start);
            result.push(node.end - 1);
        }
    }
}