UNPKG

next

Version:

The React Framework

232 lines (231 loc) • 14.6 kB
import { bold, cyan, red, yellow } from '../picocolors'; import path from 'path'; // eslint typescript has a bug with TS enums /* eslint-disable no-shadow */ export var DiagnosticCategory = /*#__PURE__*/ function(DiagnosticCategory) { DiagnosticCategory[DiagnosticCategory["Warning"] = 0] = "Warning"; DiagnosticCategory[DiagnosticCategory["Error"] = 1] = "Error"; DiagnosticCategory[DiagnosticCategory["Suggestion"] = 2] = "Suggestion"; DiagnosticCategory[DiagnosticCategory["Message"] = 3] = "Message"; return DiagnosticCategory; }({}); function getFormattedLinkDiagnosticMessageText(diagnostic) { const message = diagnostic.messageText; if (typeof message === 'string' && diagnostic.code === 2322) { const match = message.match(/Type '"(.+)"' is not assignable to type 'RouteImpl<.+> \| UrlObject'\./) || message.match(/Type '"(.+)"' is not assignable to type 'UrlObject \| RouteImpl<.+>'\./); if (match) { const [, href] = match; return `"${bold(href)}" is not an existing route. If it is intentional, please type it explicitly with \`as Route\`.`; } else if (message === "Type 'string' is not assignable to type 'UrlObject'.") { var _diagnostic_relatedInformation_, _diagnostic_relatedInformation; const relatedMessage = (_diagnostic_relatedInformation = diagnostic.relatedInformation) == null ? void 0 : (_diagnostic_relatedInformation_ = _diagnostic_relatedInformation[0]) == null ? void 0 : _diagnostic_relatedInformation_.messageText; if (typeof relatedMessage === 'string' && relatedMessage.match(/The expected type comes from property 'href' which is declared here on type 'IntrinsicAttributes & /)) { return `Invalid \`href\` property of \`Link\`: the route does not exist. If it is intentional, please type it explicitly with \`as Route\`.`; } } } else if (typeof message === 'string' && diagnostic.code === 2820) { const match = message.match(/Type '"(.+)"' is not assignable to type 'RouteImpl<.+> \| UrlObject'\. Did you mean '"(.+)"'?/) || message.match(/Type '"(.+)"' is not assignable to type 'UrlObject \| RouteImpl<.+>'\. Did you mean '"(.+)"'?/); if (match) { const [, href, suggestion] = match; return `"${bold(href)}" is not an existing route. Did you mean "${bold(suggestion)}" instead? If it is intentional, please type it explicitly with \`as Route\`.`; } } } function getFormattedLayoutAndPageDiagnosticMessageText(relativeSourceFilepath, diagnostic) { const message = typeof diagnostic.messageText === 'string' ? diagnostic : diagnostic.messageText; const messageText = message.messageText; if (typeof messageText === 'string') { const type = /page\.[^.]+$/.test(relativeSourceFilepath) ? 'Page' : /route\.[^.]+$/.test(relativeSourceFilepath) ? 'Route' : 'Layout'; // Reference of error codes: // https://github.com/Microsoft/TypeScript/blob/main/src/compiler/diagnosticMessages.json switch(message.code){ case 2344: const filepathAndType = messageText.match(/typeof import\("(.+)"\)/); if (filepathAndType) { let main = `${type} "${bold(relativeSourceFilepath)}" does not match the required types of a Next.js ${type}.`; function processNext(indent, next) { if (!next) return; for (const item of next){ switch(item.code){ case 2200: const mismatchedField = item.messageText.match(/The types of '(.+)'/); if (mismatchedField) { main += '\n' + ' '.repeat(indent * 2); main += `"${bold(mismatchedField[1])}" has the wrong type:`; } break; case 2322: const types = item.messageText.match(/Type '(.+)' is not assignable to type '(.+)'./); if (types) { main += '\n' + ' '.repeat(indent * 2); if (types[2] === 'PageComponent' || types[2] === 'LayoutComponent') { main += `The exported ${type} component isn't correctly typed.`; } else { main += `Expected "${bold(types[2].replace('"__invalid_negative_number__"', 'number (>= 0)'))}", got "${bold(types[1])}".`; } } break; case 2326: const invalidConfig = item.messageText.match(/Types of property '(.+)' are incompatible\./); main += '\n' + ' '.repeat(indent * 2); main += `Invalid configuration${invalidConfig ? ` "${bold(invalidConfig[1])}"` : ''}:`; break; case 2530: const invalidField = item.messageText.match(/Property '(.+)' is incompatible with index signature/); if (invalidField) { main += '\n' + ' '.repeat(indent * 2); main += `"${bold(invalidField[1])}" is not a valid ${type} export field.`; } return; case 2739: const invalidProp = item.messageText.match(/Type '(.+)' is missing the following properties from type '(.+)'/); if (invalidProp) { if (invalidProp[1] === 'LayoutProps' || invalidProp[1] === 'PageProps') { main += '\n' + ' '.repeat(indent * 2); main += `Prop "${invalidProp[2]}" is incompatible with the ${type}.`; } } break; case 2559: const invalid = item.messageText.match(/Type '(.+)' has/); if (invalid) { main += '\n' + ' '.repeat(indent * 2); main += `Type "${bold(invalid[1])}" isn't allowed.`; } break; case 2741: const incompatPageProp = item.messageText.match(/Property '(.+)' is missing in type 'PageProps'/); if (incompatPageProp) { main += '\n' + ' '.repeat(indent * 2); main += `Prop "${bold(incompatPageProp[1])}" will never be passed. Remove it from the component's props.`; } else { const extraLayoutProp = item.messageText.match(/Property '(.+)' is missing in type 'LayoutProps' but required in type '(.+)'/); if (extraLayoutProp) { main += '\n' + ' '.repeat(indent * 2); main += `Prop "${bold(extraLayoutProp[1])}" is not valid for this Layout, remove it to fix.`; } } break; default: } processNext(indent + 1, item.next); } } if ('next' in message) processNext(1, message.next); return main; } const invalidExportFnArg = messageText.match(/Type 'OmitWithTag<(.+), .+, "(.+)">' does not satisfy the constraint/); if (invalidExportFnArg) { const main = `${type} "${bold(relativeSourceFilepath)}" has an invalid "${bold(invalidExportFnArg[2])}" export:\n Type "${bold(invalidExportFnArg[1])}" is not valid.`; return main; } function processNextItems(indent, next) { if (!next) return ''; let result = ''; for (const item of next){ switch(item.code){ case 2322: const types = item.messageText.match(/Type '(.+)' is not assignable to type '(.+)'./); if (types) { result += '\n' + ' '.repeat(indent * 2); result += `Expected "${bold(types[2])}", got "${bold(types[1])}".`; } break; default: } result += processNextItems(indent + 1, item.next); } return result; } const invalidParamFn = messageText.match(/Type '{ __tag__: (.+); __param_position__: "(.*)"; __param_type__: (.+); }' does not satisfy/); if (invalidParamFn) { let main = `${type} "${bold(relativeSourceFilepath)}" has an invalid ${invalidParamFn[1]} export:\n Type "${bold(invalidParamFn[3])}" is not a valid type for the function's ${invalidParamFn[2]} argument.`; if ('next' in message) main += processNextItems(1, message.next); return main; } const invalidExportFnReturn = messageText.match(/Type '{ __tag__: "(.+)"; __return_type__: (.+); }' does not satisfy/); if (invalidExportFnReturn) { let main = `${type} "${bold(relativeSourceFilepath)}" has an invalid export:\n "${bold(invalidExportFnReturn[2])}" is not a valid ${invalidExportFnReturn[1]} return type:`; if ('next' in message) main += processNextItems(1, message.next); return main; } break; case 2345: const filepathAndInvalidExport = messageText.match(/'typeof import\("(.+)"\)'.+Impossible<"(.+)">/); if (filepathAndInvalidExport) { const main = `${type} "${bold(relativeSourceFilepath)}" exports an invalid "${bold(filepathAndInvalidExport[2])}" field. ${type} should only export a default React component and configuration options. Learn more: https://nextjs.org/docs/messages/invalid-segment-export`; return main; } break; case 2559: const invalid = messageText.match(/Type '(.+)' has no properties in common with type '(.+)'/); if (invalid) { const main = `${type} "${bold(relativeSourceFilepath)}" contains an invalid type "${bold(invalid[1])}" as ${invalid[2]}.`; return main; } break; default: } } } function getAppEntrySourceFilePath(baseDir, diagnostic) { var _diagnostic_file_text_trim_match, _diagnostic_file; const sourceFilepath = ((_diagnostic_file = diagnostic.file) == null ? void 0 : (_diagnostic_file_text_trim_match = _diagnostic_file.text.trim().match(/^\/\/ File: (.+)\n/)) == null ? void 0 : _diagnostic_file_text_trim_match[1]) || ''; return path.relative(baseDir, sourceFilepath); } export function getFormattedDiagnostic(ts, baseDir, distDir, diagnostic, isAppDirEnabled) { var _diagnostic_file; // If the error comes from .next/types/, we handle it specially. const isLayoutOrPageError = isAppDirEnabled && ((_diagnostic_file = diagnostic.file) == null ? void 0 : _diagnostic_file.fileName.startsWith(path.join(baseDir, distDir, 'types'))); let message = ''; const appPath = isLayoutOrPageError ? getAppEntrySourceFilePath(baseDir, diagnostic) : null; const linkReason = getFormattedLinkDiagnosticMessageText(diagnostic); const appReason = !linkReason && isLayoutOrPageError && appPath ? getFormattedLayoutAndPageDiagnosticMessageText(appPath, diagnostic) : null; const reason = linkReason || appReason || ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); const category = diagnostic.category; switch(category){ // Warning case 0: { message += yellow(bold('Type warning')) + ': '; break; } // Error case 1: { message += red(bold('Type error')) + ': '; break; } // 2 = Suggestion, 3 = Message case 2: case 3: default: { message += cyan(bold(category === 2 ? 'Suggestion' : 'Info')) + ': '; break; } } message += reason + '\n'; if (!isLayoutOrPageError && diagnostic.file) { const { codeFrameColumns } = require('next/dist/compiled/babel/code-frame'); const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); const line = pos.line + 1; const character = pos.character + 1; let fileName = path.posix.normalize(path.relative(baseDir, diagnostic.file.fileName).replace(/\\/g, '/')); if (!fileName.startsWith('.')) { fileName = './' + fileName; } message = cyan(fileName) + ':' + yellow(line.toString()) + ':' + yellow(character.toString()) + '\n' + message; message += '\n' + codeFrameColumns(diagnostic.file.getFullText(diagnostic.file.getSourceFile()), { start: { line: line, column: character } }, { forceColor: true }); } else if (isLayoutOrPageError && appPath) { message = cyan(appPath) + '\n' + message; } return message; } //# sourceMappingURL=diagnosticFormatter.js.map