Skip to content

Instantly share code, notes, and snippets.

@swain
Created September 21, 2023 17:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swain/bd47b9da148bc295631f73aca7eb44bf to your computer and use it in GitHub Desktop.
Save swain/bd47b9da148bc295631f73aca7eb44bf to your computer and use it in GitHub Desktop.
Script for fixing non-null assertions
import * as fs from 'fs';
import * as ts from 'typescript';
import glob from 'glob';
import { sortBy, uniq } from 'lodash';
/**
* Inserts the specified `text` at the specified `line` and `character` in the file at `filepath`.
*/
const insertTextAtLine = (opts: {
filepath: string;
line: number;
text: string;
}) => {
const contents = fs.readFileSync(opts.filepath, 'utf8');
const lines = contents.split('\n');
const modified = [
...lines.slice(0, opts.line),
opts.text,
...lines.slice(opts.line),
];
fs.writeFileSync(opts.filepath, modified.join('\n'));
};
function isLocationInJSX(opts: {
filepath: string;
contents: string;
line: number;
character: number;
}): boolean {
const sourceFile = ts.createSourceFile(
opts.filepath,
opts.contents,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX,
);
const position = ts.getPositionOfLineAndCharacter(
sourceFile,
opts.line,
opts.character,
);
const node = findNodeAtPosition(sourceFile, position);
return isJSXNode(node);
}
function findNodeAtPosition(
sourceFile: ts.SourceFile,
position: number,
): ts.Node | undefined {
function visit(node: ts.Node): ts.Node | undefined {
if (position >= node.getStart() && position < node.getEnd()) {
return ts.forEachChild(node, visit) || node;
}
}
return visit(sourceFile);
}
function isJSXNode(node: ts.Node | undefined): boolean {
if (!node) return false;
if (
ts.isJsxElement(node) ||
ts.isJsxFragment(node) ||
ts.isJsxText(node) ||
(ts.isJsxExpression(node) && !ts.isJsxAttribute(node.parent))
) {
return true;
}
return false;
}
const files = glob.sync(`${__dirname}/src/**/*.ts{,x}`);
const program = ts.createProgram(files, {
lib: ['dom', 'es2015', 'es2016.array.include', 'ES2019.Array'],
outDir: './dist',
rootDir: '.',
baseUrl: '.',
noImplicitAny: false,
strictNullChecks: true,
module: ts.ModuleKind.ES2020,
noUnusedParameters: true,
allowSyntheticDefaultImports: true,
esModuleInterop: true,
noImplicitThis: true,
alwaysStrict: true,
strictFunctionTypes: true,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
pretty: true,
inlineSourceMap: true,
inlineSources: true,
jsx: ts.JsxEmit.React,
target: ts.ScriptTarget.ES2020,
skipLibCheck: true,
resolveJsonModule: true,
noUnusedLocals: false,
allowJs: true,
noEmit: true,
});
const emitResult = program.emit();
const diagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
console.log('DIAGNOSTICS: ', diagnostics.length);
// map of filename -> line, char of errors
const errorLocations: Record<string, [number, number][]> = {};
for (const diag of diagnostics) {
if (!diag.file) {
continue;
}
if (diag.category !== ts.DiagnosticCategory.Error) {
console.log('Skipping non-error diagnostic');
continue;
}
// This is "could not find a declaration file"
if (diag.code === 7016) {
continue;
}
// This is "implicit any"
if (diag.code === 7006) {
continue;
}
if (diag.code === 2739) {
continue;
}
const { line, character } = ts.getLineAndCharacterOfPosition(
diag.file,
diag.start!,
);
if (!errorLocations[diag.file.fileName]) {
errorLocations[diag.file.fileName] = [];
}
errorLocations[diag.file.fileName].push([line, character]);
}
for (const [filepath, locations] of Object.entries(errorLocations)) {
const lines = sortBy(uniq(locations.map(([line]) => line))).reverse();
const contents = fs.readFileSync(filepath, 'utf8');
for (const line of lines) {
if (
filepath.includes('.tsx') &&
isLocationInJSX({ filepath, contents, line, character: 0 })
) {
insertTextAtLine({
filepath,
line,
text: '{/* @ts-expect-error This directive was added as part of a bulk-change to enable strictNullChecks. */}',
});
} else {
insertTextAtLine({
filepath,
line,
text: '// @ts-expect-error This directive was added as part of a bulk-change to enable strictNullChecks.',
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment