@xtrek/ts-migrate-plugins
Version:
Set of codemods, which are doing transformation of js/jsx to ts/tsx
196 lines • 9.17 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-use-before-define, @typescript-eslint/no-use-before-define */
const typescript_1 = __importDefault(require("typescript"));
const type_guards_1 = require("../utils/type-guards");
const updateSourceText_1 = __importDefault(require("../utils/updateSourceText"));
const validateOptions_1 = require("../utils/validateOptions");
const optionProperties = {
useTsIgnore: { type: 'boolean' },
};
const tsIgnorePlugin = {
name: 'ts-ignore',
run({ getLanguageService, fileName, sourceFile, options }) {
const diagnostics = getLanguageService()
.getSemanticDiagnostics(fileName)
.filter(type_guards_1.isDiagnosticWithLinePosition);
return getTextWithIgnores(sourceFile, diagnostics, options);
},
validate: validateOptions_1.createValidate(optionProperties),
};
exports.default = tsIgnorePlugin;
const TS_IGNORE_MESSAGE_LIMIT = 50;
function getTextWithIgnores(sourceFile, diagnostics, options) {
const { text } = sourceFile;
const updates = [];
const isIgnored = {};
diagnostics.forEach((diagnostic) => {
const { line: diagnosticLine } = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, diagnostic.start);
const { code } = diagnostic;
const messageText = typeof diagnostic.messageText === 'string'
? diagnostic.messageText
: diagnostic.messageText.messageText;
const messageLines = messageText
.split('\n')
.map((l) => l.trim())
.filter(Boolean);
const message = messageLines[messageLines.length - 1];
const errorExpression = options.useTsIgnore ? 'ts-ignore' : `ts-expect-error`;
const tsIgnoreCommentText = `@${errorExpression} ts-migrate(${code}) FIXME: ${message.length > TS_IGNORE_MESSAGE_LIMIT
? `${message.slice(0, TS_IGNORE_MESSAGE_LIMIT)}... Remove this comment to see the full error message`
: message}`;
if (!isIgnored[diagnosticLine]) {
let commentLine = diagnosticLine;
let pos = getStartOfLinePos(commentLine, sourceFile);
while (commentLine > 0) {
const prevLine = commentLine - 1;
const prevLinePos = getStartOfLinePos(prevLine, sourceFile);
const prevLineText = text.slice(prevLinePos, pos - 1);
const prevLineStartsWithEslintComment = /^ *\/\/ *eslint/.test(prevLineText);
if (!prevLineStartsWithEslintComment)
break;
commentLine = prevLine;
pos = prevLinePos;
}
// Include leading whitespace
let ws = '';
let i = pos;
while (sourceFile.text[i] === ' ') {
i += 1;
ws += ' ';
}
if (inTemplateExpressionText(sourceFile, pos)) {
const node = findDiagnosticNode(diagnostic, sourceFile);
if (node) {
updates.push({
kind: 'insert',
index: node.pos,
text: `${ws}${typescript_1.default.sys.newLine}// ${tsIgnoreCommentText}${text[node.pos] !== typescript_1.default.sys.newLine ? typescript_1.default.sys.newLine : ''}`,
});
}
else {
throw new Error(`Failed to add @${errorExpression} within template expression.`);
}
}
else if (inJsxText(sourceFile, pos)) {
updates.push({
kind: 'insert',
index: pos,
text: `${ws}{/* ${tsIgnoreCommentText} */}${typescript_1.default.sys.newLine}`,
});
}
else if (onMultilineConditionalTokenLine(sourceFile, diagnostic.start)) {
updates.push({
kind: 'insert',
index: getConditionalCommentPos(sourceFile, diagnostic.start),
text: ` // ${tsIgnoreCommentText}${typescript_1.default.sys.newLine}${ws} `,
});
}
else {
let skip = false;
if (commentLine > 1) {
const prevLineText = text.slice(getStartOfLinePos(commentLine - 1, sourceFile), getStartOfLinePos(commentLine, sourceFile));
if (/\bwebpackChunkName\b/.test(prevLineText)) {
skip = true;
}
}
if (!skip) {
updates.push({
kind: 'insert',
index: pos,
text: `${ws}// ${tsIgnoreCommentText}${typescript_1.default.sys.newLine}`,
});
}
}
isIgnored[diagnosticLine] = true;
}
});
return updateSourceText_1.default(text, updates);
}
function findDiagnosticNode(diagnostic, sourceFile) {
const visitor = (node) => isDiagnosticNode(node, diagnostic, sourceFile) ? node : typescript_1.default.forEachChild(node, visitor);
return visitor(sourceFile);
}
function isDiagnosticNode(node, diagnostic, sourceFile) {
return (node.getStart(sourceFile) === diagnostic.start &&
node.getEnd() === diagnostic.start + diagnostic.length);
}
function inJsxText(sourceFile, pos) {
const visitor = (node) => {
if (node.pos <= pos && pos < node.end && (typescript_1.default.isJsxElement(node) || typescript_1.default.isJsxFragment(node))) {
const isJsxTextChild = node.children.some((child) => typescript_1.default.isJsxText(child) && child.pos <= pos && pos < child.end);
if (isJsxTextChild) {
return true;
}
}
return typescript_1.default.forEachChild(node, visitor);
};
return !!typescript_1.default.forEachChild(sourceFile, visitor);
}
function inTemplateExpressionText(sourceFile, pos) {
const visitor = (node) => {
if (node.pos <= pos && pos < node.end && typescript_1.default.isTemplateExpression(node)) {
const inHead = node.head.pos <= pos && pos < node.head.end;
const inMiddleOrTail = node.templateSpans.some((span) => span.literal.pos <= pos && pos < span.literal.end);
if (inHead || inMiddleOrTail) {
return true;
}
}
return typescript_1.default.forEachChild(node, visitor);
};
return !!typescript_1.default.forEachChild(sourceFile, visitor);
}
function getConditionalExpressionAtPos(sourceFile, pos) {
const visitor = (node) => {
if (node.pos <= pos && pos < node.end && typescript_1.default.isConditionalExpression(node)) {
return node;
}
return typescript_1.default.forEachChild(node, visitor);
};
return typescript_1.default.forEachChild(sourceFile, visitor);
}
function visitConditionalExpressionWhen(node, pos, visitor) {
if (!node)
return visitor.otherwise();
const inWhenTrue = node.whenTrue.pos <= pos && pos < node.whenTrue.end;
if (inWhenTrue)
return visitor.whenTrue(node);
const inWhenFalse = node.whenFalse.pos <= pos && pos < node.whenFalse.end;
if (inWhenFalse)
return visitor.whenFalse(node);
return visitor.otherwise();
}
function onMultilineConditionalTokenLine(sourceFile, pos) {
const conditionalExpression = getConditionalExpressionAtPos(sourceFile, pos);
// Not in a conditional expression.
if (!conditionalExpression)
return false;
const { line: questionTokenLine } = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, conditionalExpression.questionToken.end);
const { line: colonTokenLine } = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, conditionalExpression.colonToken.end);
// Single line conditional expression.
if (questionTokenLine === colonTokenLine)
return false;
const { line } = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, pos);
return visitConditionalExpressionWhen(conditionalExpression, pos, {
// On question token line of multiline conditional expression.
whenTrue: () => line === questionTokenLine,
// On colon token line of multiline conditional expression.
whenFalse: () => line === colonTokenLine,
otherwise: () => false,
});
}
function getConditionalCommentPos(sourceFile, pos) {
return visitConditionalExpressionWhen(getConditionalExpressionAtPos(sourceFile, pos), pos, {
whenTrue: (node) => node.questionToken.end,
whenFalse: (node) => node.colonToken.end,
otherwise: () => pos,
});
}
/** Get position at start of zero-indexed line number in the given source file. */
function getStartOfLinePos(line, sourceFile) {
return typescript_1.default.getPositionOfLineAndCharacter(sourceFile, line, 0);
}
//# sourceMappingURL=ts-ignore.js.map