Created
June 6, 2019 00:26
-
-
Save weswigham/f477373b6124cc43df5356c75f66911b to your computer and use it in GitHub Desktop.
Quick and dirty gist for using the compiler API to apply all available import fixes for a project
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Usage: node ./apply-all-import-fixes.js ./path/to/index.ts | |
import * as ts from "typescript/lib/tsserverlibrary"; | |
import * as fs from "fs"; | |
import * as path from "path"; | |
class Logger implements ts.server.Logger { // tslint:disable-line no-unnecessary-qualifier | |
private fd = -1; | |
private seq = 0; | |
private inGroup = false; | |
private firstInGroup = true; | |
constructor(private readonly logFilename: string, | |
private readonly traceToConsole: boolean, | |
private readonly level: ts.server.LogLevel) { | |
if (this.logFilename) { | |
try { | |
this.fd = fs.openSync(this.logFilename, "w"); | |
} | |
catch (_) { | |
// swallow the error and keep logging disabled if file cannot be opened | |
} | |
} | |
} | |
static padStringRight(str: string, padding: string) { | |
return (str + padding).slice(0, padding.length); | |
} | |
close() { | |
if (this.fd >= 0) { | |
fs.close(this.fd, () => void 0); | |
} | |
} | |
getLogFileName() { | |
return this.logFilename; | |
} | |
perftrc(s: string) { | |
this.msg(s, ts.server.Msg.Perf); | |
} | |
info(s: string) { | |
this.msg(s, ts.server.Msg.Info); | |
} | |
err(s: string) { | |
this.msg(s, ts.server.Msg.Err); | |
} | |
startGroup() { | |
this.inGroup = true; | |
this.firstInGroup = true; | |
} | |
endGroup() { | |
this.inGroup = false; | |
} | |
loggingEnabled() { | |
return !!this.logFilename || this.traceToConsole; | |
} | |
hasLevel(level: ts.server.LogLevel) { | |
return this.loggingEnabled() && this.level >= level; | |
} | |
msg(s: string, type: ts.server.Msg = ts.server.Msg.Err) { | |
if (!this.canWrite) return; | |
s = `[${new Date().getUTCDate()}] ${s}\n`; | |
if (!this.inGroup || this.firstInGroup) { | |
const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); | |
s = prefix + s; | |
} | |
this.write(s); | |
if (!this.inGroup) { | |
this.seq++; | |
} | |
} | |
private get canWrite() { | |
return this.fd >= 0 || this.traceToConsole; | |
} | |
private write(s: string) { | |
if (this.fd >= 0) { | |
const buf = Buffer.from(s); | |
// tslint:disable-next-line no-null-keyword | |
fs.writeSync(this.fd, buf, 0, buf.length, /*position*/ null!); // TODO: GH#18217 | |
} | |
if (this.traceToConsole) { | |
console.warn(s); | |
} | |
} | |
} | |
const projectService = new ts.server.ProjectService({ | |
host: ts.sys as ts.server.ServerHost, | |
typingsInstaller: ts.server.nullTypingsInstaller, | |
logger: new Logger("./log.log", true, ts.server.LogLevel.normal), | |
cancellationToken: ts.server.nullCancellationToken, | |
useInferredProjectPerProjectRoot: true, | |
useSingleInferredProject: false | |
}); | |
projectService.openClientFile(path.join(process.cwd(), process.argv[2])); | |
const lshost = projectService.configuredProjects.values().next().value; | |
const ls = lshost.getLanguageService(); | |
const formatSettings = ts.getDefaultFormatCodeSettings("\n"); | |
for (const file of ls.getProgram().getSourceFiles()) { | |
const diags = ls.getSemanticDiagnostics(file.fileName); | |
const allfixes: ts.FileTextChanges[] = []; | |
for (const diag of diags) { | |
if (diag.code !== 2304) continue; // Code for "cannot find name" errors | |
const fixes = ls.getCodeFixesAtPosition(file.fileName, diag.start, diag.start + diag.length, [diag.code], formatSettings, { quotePreference: "double" }); | |
const fix = fixes[0]; | |
allfixes.push(...fix.changes); | |
} | |
const newFileContent = applyEdits(file.text, file.fileName, allfixes); | |
fs.writeFileSync(file.fileName, newFileContent); | |
} | |
function applyEdits(text: string, textFilename: string, edits: ReadonlyArray<ts.FileTextChanges>): string { | |
for (const { fileName, textChanges } of edits) { | |
if (fileName !== textFilename) { | |
continue; | |
} | |
for (let i = textChanges.length - 1; i >= 0; i--) { | |
const { newText, span: { start, length } } = textChanges[i]; | |
text = text.slice(0, start) + newText + text.slice(start + length); | |
} | |
} | |
return text; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this @weswigham, this is great!
I'm trying to do something similar - I would like to write a command line program to call the SQL formatting functionality in
typescript-sql-tagged-template-plugin
(when SQL is in inline tagged template literals).I tried combining your program here with some code to run
getFormattingEditsForDocument
, but it doesn't seem to be running the correspondinggetFormattingEditsFor
methods in the plugin 🤔 :https://github.com/karlhorky/ts-language-service-plugin-formatting-cli
Is it maybe related to the following comment from this Stack Overflow question:
I'm not completely versed with TS internals, so I don't know what I should do with this information, but I guess if it seems like a good lead, I can investigate further...