1*8d307f52SEvan Bacon/** 2*8d307f52SEvan Bacon * Copyright (c) 2022 Expo, Inc. 3*8d307f52SEvan Bacon * Copyright (c) 2015-present, Facebook, Inc. 4*8d307f52SEvan Bacon * 5*8d307f52SEvan Bacon * This source code is licensed under the MIT license found in the 6*8d307f52SEvan Bacon * LICENSE file in the root directory of this source tree. 7*8d307f52SEvan Bacon * 8*8d307f52SEvan Bacon * Based on https://github.com/facebook/create-react-app/blob/b172b5e/packages/react-dev-utils/ModuleNotFoundPlugin.js 9*8d307f52SEvan Bacon * But with Node LTS support. 10*8d307f52SEvan Bacon */ 11*8d307f52SEvan Bacon 12*8d307f52SEvan Baconimport type { Stats } from 'webpack'; 13*8d307f52SEvan Bacon 14*8d307f52SEvan Baconconst friendlySyntaxErrorLabel = 'Syntax error:'; 15*8d307f52SEvan Bacon 16*8d307f52SEvan Baconfunction isLikelyASyntaxError(message: string): boolean { 17*8d307f52SEvan Bacon return message.indexOf(friendlySyntaxErrorLabel) !== -1; 18*8d307f52SEvan Bacon} 19*8d307f52SEvan Bacon 20*8d307f52SEvan Bacon// Cleans up webpack error messages. 21*8d307f52SEvan Baconfunction formatMessage(message: string | { message: string } | { message: string }[]) { 22*8d307f52SEvan Bacon let lines: string[] = []; 23*8d307f52SEvan Bacon 24*8d307f52SEvan Bacon if (typeof message === 'string') { 25*8d307f52SEvan Bacon lines = message.split('\n'); 26*8d307f52SEvan Bacon } else if ('message' in message) { 27*8d307f52SEvan Bacon lines = message['message'].split('\n'); 28*8d307f52SEvan Bacon } else if (Array.isArray(message)) { 29*8d307f52SEvan Bacon message.forEach((message) => { 30*8d307f52SEvan Bacon if ('message' in message) { 31*8d307f52SEvan Bacon lines = message['message'].split('\n'); 32*8d307f52SEvan Bacon } 33*8d307f52SEvan Bacon }); 34*8d307f52SEvan Bacon } 35*8d307f52SEvan Bacon 36*8d307f52SEvan Bacon // Strip webpack-added headers off errors/warnings 37*8d307f52SEvan Bacon // https://github.com/webpack/webpack/blob/master/lib/ModuleError.js 38*8d307f52SEvan Bacon lines = lines.filter((line) => !/Module [A-z ]+\(from/.test(line)); 39*8d307f52SEvan Bacon 40*8d307f52SEvan Bacon // Transform parsing error into syntax error 41*8d307f52SEvan Bacon // TODO: move this to our ESLint formatter? 42*8d307f52SEvan Bacon lines = lines.map((line) => { 43*8d307f52SEvan Bacon const parsingError = /Line (\d+):(?:(\d+):)?\s*Parsing error: (.+)$/.exec(line); 44*8d307f52SEvan Bacon if (!parsingError) { 45*8d307f52SEvan Bacon return line; 46*8d307f52SEvan Bacon } 47*8d307f52SEvan Bacon const [, errorLine, errorColumn, errorMessage] = parsingError; 48*8d307f52SEvan Bacon return `${friendlySyntaxErrorLabel} ${errorMessage} (${errorLine}:${errorColumn})`; 49*8d307f52SEvan Bacon }); 50*8d307f52SEvan Bacon 51*8d307f52SEvan Bacon message = lines.join('\n'); 52*8d307f52SEvan Bacon // Smoosh syntax errors (commonly found in CSS) 53*8d307f52SEvan Bacon message = message.replace( 54*8d307f52SEvan Bacon /SyntaxError\s+\((\d+):(\d+)\)\s*(.+?)\n/g, 55*8d307f52SEvan Bacon `${friendlySyntaxErrorLabel} $3 ($1:$2)\n` 56*8d307f52SEvan Bacon ); 57*8d307f52SEvan Bacon // Clean up export errors 58*8d307f52SEvan Bacon message = message.replace( 59*8d307f52SEvan Bacon /^.*export '(.+?)' was not found in '(.+?)'.*$/gm, 60*8d307f52SEvan Bacon `Attempted import error: '$1' is not exported from '$2'.` 61*8d307f52SEvan Bacon ); 62*8d307f52SEvan Bacon message = message.replace( 63*8d307f52SEvan Bacon /^.*export 'default' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm, 64*8d307f52SEvan Bacon `Attempted import error: '$2' does not contain a default export (imported as '$1').` 65*8d307f52SEvan Bacon ); 66*8d307f52SEvan Bacon message = message.replace( 67*8d307f52SEvan Bacon /^.*export '(.+?)' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm, 68*8d307f52SEvan Bacon `Attempted import error: '$1' is not exported from '$3' (imported as '$2').` 69*8d307f52SEvan Bacon ); 70*8d307f52SEvan Bacon lines = message.split('\n'); 71*8d307f52SEvan Bacon 72*8d307f52SEvan Bacon // Remove leading newline 73*8d307f52SEvan Bacon if (lines.length > 2 && lines[1].trim() === '') { 74*8d307f52SEvan Bacon lines.splice(1, 1); 75*8d307f52SEvan Bacon } 76*8d307f52SEvan Bacon // Clean up file name 77*8d307f52SEvan Bacon lines[0] = lines[0].replace(/^(.*) \d+:\d+-\d+$/, '$1'); 78*8d307f52SEvan Bacon 79*8d307f52SEvan Bacon // Cleans up verbose "module not found" messages for files and packages. 80*8d307f52SEvan Bacon if (lines[1] && lines[1].indexOf('Module not found: ') === 0) { 81*8d307f52SEvan Bacon lines = [ 82*8d307f52SEvan Bacon lines[0], 83*8d307f52SEvan Bacon lines[1] 84*8d307f52SEvan Bacon .replace('Error: ', '') 85*8d307f52SEvan Bacon .replace('Module not found: Cannot find file:', 'Cannot find file:'), 86*8d307f52SEvan Bacon ]; 87*8d307f52SEvan Bacon } 88*8d307f52SEvan Bacon 89*8d307f52SEvan Bacon // Add helpful message for users trying to use Sass for the first time 90*8d307f52SEvan Bacon if (lines[1] && lines[1].match(/Cannot find module.+sass/)) { 91*8d307f52SEvan Bacon lines[1] = 'To import Sass files, you first need to install sass.\n'; 92*8d307f52SEvan Bacon lines[1] += 'Run `npm install sass` or `yarn add sass` inside your workspace.'; 93*8d307f52SEvan Bacon } 94*8d307f52SEvan Bacon 95*8d307f52SEvan Bacon message = lines.join('\n'); 96*8d307f52SEvan Bacon // Internal stacks are generally useless so we strip them... with the 97*8d307f52SEvan Bacon // exception of stacks containing `webpack:` because they're normally 98*8d307f52SEvan Bacon // from user code generated by webpack. For more information see 99*8d307f52SEvan Bacon // https://github.com/facebook/create-react-app/pull/1050 100*8d307f52SEvan Bacon message = message.replace(/^\s*at\s((?!webpack:).)*:\d+:\d+[\s)]*(\n|$)/gm, ''); // at ... ...:x:y 101*8d307f52SEvan Bacon message = message.replace(/^\s*at\s<anonymous>(\n|$)/gm, ''); // at <anonymous> 102*8d307f52SEvan Bacon lines = message.split('\n'); 103*8d307f52SEvan Bacon 104*8d307f52SEvan Bacon // Remove duplicated newlines 105*8d307f52SEvan Bacon lines = lines.filter( 106*8d307f52SEvan Bacon (line, index, arr) => index === 0 || line.trim() !== '' || line.trim() !== arr[index - 1].trim() 107*8d307f52SEvan Bacon ); 108*8d307f52SEvan Bacon 109*8d307f52SEvan Bacon // Reassemble the message 110*8d307f52SEvan Bacon message = lines.join('\n'); 111*8d307f52SEvan Bacon return message.trim(); 112*8d307f52SEvan Bacon} 113*8d307f52SEvan Bacon 114*8d307f52SEvan Baconexport function formatWebpackMessages(json?: Stats.ToJsonOutput) { 115*8d307f52SEvan Bacon const formattedErrors = json?.errors?.map(formatMessage); 116*8d307f52SEvan Bacon const formattedWarnings = json?.warnings?.map(formatMessage); 117*8d307f52SEvan Bacon const result = { errors: formattedErrors, warnings: formattedWarnings }; 118*8d307f52SEvan Bacon if (result.errors?.some(isLikelyASyntaxError)) { 119*8d307f52SEvan Bacon // If there are any syntax errors, show just them. 120*8d307f52SEvan Bacon result.errors = result.errors.filter(isLikelyASyntaxError); 121*8d307f52SEvan Bacon } 122*8d307f52SEvan Bacon return result; 123*8d307f52SEvan Bacon} 124