UNPKG

@xtrek/ts-migrate-plugins

Version:

Set of codemods, which are doing transformation of js/jsx to ts/tsx

196 lines 9.17 kB
"use strict"; 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