From b931cbecbf2ae936b14b481a62992a0ba88dd039 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 13 May 2020 17:18:45 -0400 Subject: [PATCH] Simplify CSS Errors (#12852) --- .../wellknown-errors-plugin/parseCss.ts | 38 +++++++++++++++ .../webpackModuleError.ts | 20 ++++++-- test/acceptance/ReactRefreshLogBox.test.js | 47 +++++++++++++++++++ .../css-features/test/index.test.js | 6 +-- 4 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 packages/next/build/webpack/plugins/wellknown-errors-plugin/parseCss.ts diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseCss.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseCss.ts new file mode 100644 index 0000000000..bb57651716 --- /dev/null +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseCss.ts @@ -0,0 +1,38 @@ +import Chalk from 'next/dist/compiled/chalk' +import { SimpleWebpackError } from './simpleWebpackError' + +const chalk = new Chalk.constructor({ enabled: true }) +const regexCssError = /^(?:CssSyntaxError|SyntaxError)\n\n\((\d+):(\d*)\) (.*)$/s + +export function getCssError( + fileName: string, + err: Error +): SimpleWebpackError | false { + if ( + !( + (err.name === 'CssSyntaxError' || err.name === 'SyntaxError') && + (err as any).stack === false && + !(err instanceof SyntaxError) + ) + ) { + return false + } + + // https://github.com/postcss/postcss-loader/blob/d6931da177ac79707bd758436e476036a55e4f59/src/Error.js + + const res = regexCssError.exec(err.message) + if (res) { + const [, _lineNumer, _column, reason] = res + const lineNumber = Math.max(1, parseInt(_lineNumer, 10)) + const column = Math.max(1, parseInt(_column, 10)) + + return new SimpleWebpackError( + `${chalk.cyan(fileName)}:${chalk.yellow( + lineNumber.toString() + )}:${chalk.yellow(column.toString())}`, + chalk.red.bold('Syntax error').concat(`: ${reason}`) + ) + } + + return false +} diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts index 4ab7ae5000..f68aac912b 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts @@ -2,19 +2,23 @@ import * as path from 'path' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { compilation } from 'webpack' import { getBabelError } from './parseBabel' +import { getCssError } from './parseCss' import { SimpleWebpackError } from './simpleWebpackError' function getFilename(compilation: compilation.Compilation, m: any): string { + let ctx: string | null = + compilation.compiler?.context ?? compilation.context ?? null + if (ctx !== null && typeof m.resource === 'string') { + const res = path.relative(ctx, m.resource).replace(/\\/g, path.posix.sep) + return res.startsWith('.') ? res : `.${path.posix.sep}${res}` + } + const requestShortener = compilation.requestShortener if (typeof m?.readableIdentifier === 'function') { return m.readableIdentifier(requestShortener) } - if (typeof m.resource === 'string') { - const res = path.relative(compilation.context, m.resource) - return res.startsWith('.') ? res : `.${path.sep}${res}` - } - return m.request ?? '' + return m.request ?? m.userRequest ?? '' } export function getModuleBuildError( @@ -34,10 +38,16 @@ export function getModuleBuildError( const err: Error = input.error const sourceFilename = getFilename(compilation, input.module) + const babel = getBabelError(sourceFilename, err) if (babel !== false) { return babel } + const css = getCssError(sourceFilename, err) + if (css !== false) { + return css + } + return false } diff --git a/test/acceptance/ReactRefreshLogBox.test.js b/test/acceptance/ReactRefreshLogBox.test.js index 0fab9b385d..1037c19dd1 100644 --- a/test/acceptance/ReactRefreshLogBox.test.js +++ b/test/acceptance/ReactRefreshLogBox.test.js @@ -713,3 +713,50 @@ test('conversion to class component (1)', async () => { await cleanup() }) + +test('css syntax errors', async () => { + const [session, cleanup] = await sandbox() + + await session.write('index.module.css', `.button {}`) + await session.patch( + 'index.js', + ` + import './index.module.css'; + export default () => { + return ( +
+

lol

+
+ ) + } + ` + ) + + expect(await session.hasRedbox()).toBe(false) + + // Syntax error + await session.patch('index.module.css', `.button {`) + expect(await session.hasRedbox(true)).toBe(true) + const source = await session.getRedboxSource() + expect(source).toMatchInlineSnapshot(` + "./index.module.css:1:1 + Syntax error: Unclosed block + + > 1 | .button { + | ^" + `) + + // Not local error + await session.patch('index.module.css', `button {}`) + expect(await session.hasRedbox(true)).toBe(true) + const source2 = await session.getRedboxSource() + expect(source2).toMatchInlineSnapshot(` + "./index.module.css:1:1 + Syntax error: Selector \\"button\\" is not pure (pure selectors must contain at least one local class or id) + + > 1 | button {} + | ^" + `) + + await cleanup() +}) diff --git a/test/integration/css-features/test/index.test.js b/test/integration/css-features/test/index.test.js index 832783e879..b61a19ccaa 100644 --- a/test/integration/css-features/test/index.test.js +++ b/test/integration/css-features/test/index.test.js @@ -150,7 +150,7 @@ describe('Custom Properties: Fail for :root {} in CSS Modules', () => { expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') expect(stderr).toContain('pages/styles.module.css') - expect(stderr).toContain('CssSyntax error: Selector ":root" is not pure') + expect(stderr).toContain('Selector ":root" is not pure') }) }) @@ -168,7 +168,7 @@ describe('Custom Properties: Fail for global element in CSS Modules', () => { expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') expect(stderr).toContain('pages/styles.module.css') - expect(stderr).toContain('CssSyntax error: Selector "h1" is not pure') + expect(stderr).toContain('Selector "h1" is not pure') }) }) @@ -218,6 +218,6 @@ describe('CSS Modules: Importing Invalid Global CSS', () => { expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') expect(stderr).toContain('pages/styles.module.css') - expect(stderr).toContain('CssSyntax error: Selector "a" is not pure') + expect(stderr).toContain('Selector "a" is not pure') }) }) -- GitLab