137 lines
4.6 KiB
JavaScript
137 lines
4.6 KiB
JavaScript
// Target the diagnostics from the current source; preserve literals and API property names.
|
|
const fs = require('node:fs'),
|
|
path = require('node:path'),
|
|
ts = require('typescript');
|
|
const root = path.resolve(__dirname, '..');
|
|
const input = path.join(
|
|
root,
|
|
'storage/app/private/investmoz-v2/portal-audit/next-lint-diagnostics.json',
|
|
);
|
|
const raw = fs.readFileSync(input);
|
|
const data = JSON.parse(
|
|
raw
|
|
.toString(raw[0] === 255 && raw[1] === 254 ? 'utf16le' : 'utf8')
|
|
.replace(/^\uFEFF/, ''),
|
|
);
|
|
const byFile = new Map();
|
|
for (const d of data.diagnostics) {
|
|
const group = byFile.get(d.filename) || [];
|
|
group.push(d);
|
|
byFile.set(d.filename, group);
|
|
}
|
|
let changed = 0;
|
|
for (const [name, diagnostics] of byFile) {
|
|
const file = path.resolve(root, name);
|
|
if (!file.startsWith(root + path.sep))
|
|
throw Error('Target outside workspace');
|
|
let source = fs.readFileSync(file, 'utf8');
|
|
const parsed = ts.createSourceFile(
|
|
name,
|
|
source,
|
|
ts.ScriptTarget.Latest,
|
|
true,
|
|
name.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JS,
|
|
);
|
|
const edits = [];
|
|
const seen = new Set();
|
|
function edit(start, end, value) {
|
|
const key = start + ':' + end;
|
|
if (seen.has(key)) return;
|
|
seen.add(key);
|
|
edits.push({ start, end, value });
|
|
}
|
|
function walk(node, fn) {
|
|
fn(node);
|
|
ts.forEachChild(node, (n) => walk(n, fn));
|
|
}
|
|
for (const d of diagnostics) {
|
|
if (d.code === 'eslint(no-unused-vars)') {
|
|
for (const label of d.labels) {
|
|
const bytes = Buffer.from(source),
|
|
start = bytes
|
|
.subarray(0, label.span.offset)
|
|
.toString().length,
|
|
end = bytes
|
|
.subarray(0, label.span.offset + label.span.length)
|
|
.toString().length;
|
|
let id;
|
|
walk(parsed, (n) => {
|
|
if (
|
|
ts.isIdentifier(n) &&
|
|
n.getStart(parsed) === start &&
|
|
n.end === end
|
|
)
|
|
id = n;
|
|
});
|
|
if (!id) throw Error('Diagnostic no longer matches ' + name);
|
|
const alias = '_' + id.text;
|
|
const parent = id.parent;
|
|
edit(
|
|
start,
|
|
end,
|
|
ts.isShorthandPropertyAssignment(parent) ||
|
|
(ts.isBindingElement(parent) &&
|
|
!parent.propertyName &&
|
|
ts.isObjectBindingPattern(parent.parent))
|
|
? id.text + ': ' + alias
|
|
: alias,
|
|
);
|
|
}
|
|
} else if (d.code === 'typescript(no-floating-promises)') {
|
|
const span = d.labels[0].span,
|
|
bytes = Buffer.from(source),
|
|
start = bytes.subarray(0, span.offset).toString().length,
|
|
end = bytes
|
|
.subarray(0, span.offset + span.length)
|
|
.toString().length;
|
|
let statement;
|
|
walk(parsed, (n) => {
|
|
if (
|
|
ts.isExpressionStatement(n) &&
|
|
n.expression.getStart(parsed) <= start &&
|
|
n.expression.end >= end
|
|
)
|
|
statement = n;
|
|
});
|
|
if (!statement) throw Error('Promise expression not found ' + name);
|
|
edit(
|
|
statement.expression.getStart(parsed),
|
|
statement.expression.getStart(parsed),
|
|
'void ',
|
|
);
|
|
}
|
|
}
|
|
if (
|
|
name.startsWith('resources/') &&
|
|
diagnostics.some((d) => d.code === 'typescript(no-base-to-string)')
|
|
) {
|
|
walk(parsed, (n) => {
|
|
if (
|
|
ts.isCallExpression(n) &&
|
|
ts.isIdentifier(n.expression) &&
|
|
n.expression.text === 'String'
|
|
)
|
|
edit(
|
|
n.expression.getStart(parsed),
|
|
n.expression.end,
|
|
'valueText',
|
|
);
|
|
});
|
|
edit(0, 0, "import { valueText } from '@/lib/value-text';\n");
|
|
}
|
|
edits.sort((a, b) => b.start - a.start || b.end - a.end);
|
|
for (const e of edits)
|
|
source = source.slice(0, e.start) + e.value + source.slice(e.end);
|
|
if (edits.length) {
|
|
fs.writeFileSync(file, source);
|
|
changed++;
|
|
}
|
|
}
|
|
console.log(
|
|
JSON.stringify({
|
|
changed_files: changed,
|
|
diagnostic_bound_edits: true,
|
|
database_writes: 0,
|
|
}),
|
|
);
|