From 673643c19d5d87d311f30c77063fb9ec1b320d58 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:08:34 +0200 Subject: [PATCH 1/2] Generate codeframe positions for JSON5 --- flow-libs/json-source-map.js.flow | 41 ------------------- packages/core/core/package.json | 2 +- .../core/core/src/requests/EntryRequest.js | 4 +- .../core/core/src/requests/TargetRequest.js | 4 +- packages/core/diagnostic/package.json | 2 +- packages/core/diagnostic/src/diagnostic.js | 9 ++-- packages/core/utils/src/schema.js | 2 +- packages/transformers/babel/src/config.js | 7 +--- .../transformers/webextension/package.json | 4 +- .../src/WebExtensionTransformer.js | 4 +- .../transformers/webmanifest/package.json | 4 +- .../webmanifest/src/WebManifestTransformer.js | 4 +- yarn.lock | 36 +++++++++++----- 13 files changed, 46 insertions(+), 77 deletions(-) delete mode 100644 flow-libs/json-source-map.js.flow diff --git a/flow-libs/json-source-map.js.flow b/flow-libs/json-source-map.js.flow deleted file mode 100644 index eda1c8a14b2..00000000000 --- a/flow-libs/json-source-map.js.flow +++ /dev/null @@ -1,41 +0,0 @@ -//@flow strict-local - -// Derived from the README and source of json-source-map located at -// https://github.com/epoberezkin/json-source-map and -// https://github.com/epoberezkin/json-source-map/blob/v0.6.1/index.js -// Which is licensed MIT - -declare module 'json-source-map' { - declare type Position = {| - line: number, - column: number, - pos: number, - |}; - - declare export type Mapping = {| - value: Position, - valueEnd: Position, - key?: Position, - keyEnd?: Position, - |}; - - declare export default {| - parse: ( - json: string, - _?: mixed, - opts?: {|bigint: boolean|}, - ) => {| - data: any, - pointers: {|[key: string]: Mapping|}, - |}, - - stringify: ( - data: any, - _?: mixed, - space?: string | number | {|space: number, es6: boolean|}, - ) => {| - json: string, - pointers: {|[key: string]: Mapping|}, - |}, - |}; -} diff --git a/packages/core/core/package.json b/packages/core/core/package.json index 04748a7f420..a757f27d24e 100644 --- a/packages/core/core/package.json +++ b/packages/core/core/package.json @@ -24,6 +24,7 @@ "check-ts": "tsc --noEmit index.d.ts" }, "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", "@parcel/cache": "2.4.1", "@parcel/diagnostic": "2.4.1", "@parcel/events": "2.4.1", @@ -43,7 +44,6 @@ "clone": "^2.1.1", "dotenv": "^7.0.0", "dotenv-expand": "^5.1.0", - "json-source-map": "^0.6.1", "json5": "^2.2.0", "msgpackr": "^1.5.4", "nullthrows": "^1.1.1", diff --git a/packages/core/core/src/requests/EntryRequest.js b/packages/core/core/src/requests/EntryRequest.js index 2f4031e3315..a26f8abc8a8 100644 --- a/packages/core/core/src/requests/EntryRequest.js +++ b/packages/core/core/src/requests/EntryRequest.js @@ -17,7 +17,7 @@ import ThrowableDiagnostic, { getJSONSourceLocation, } from '@parcel/diagnostic'; import path from 'path'; -import jsonMap, {type Mapping} from 'json-source-map'; +import {parse, type Mapping} from '@mischnic/json-sourcemap'; import { type ProjectPath, fromProjectPath, @@ -345,7 +345,7 @@ export class EntryResolver { return { ...pkg, filePath: pkgFile, - map: jsonMap.parse(content.replace(/\t/g, ' ')), + map: parse(content, undefined, {tabWidth: 1}), }; } } diff --git a/packages/core/core/src/requests/TargetRequest.js b/packages/core/core/src/requests/TargetRequest.js index 7656169a806..f21d9bcdd95 100644 --- a/packages/core/core/src/requests/TargetRequest.js +++ b/packages/core/core/src/requests/TargetRequest.js @@ -34,7 +34,7 @@ import createParcelConfigRequest, { } from './ParcelConfigRequest'; // $FlowFixMe import browserslist from 'browserslist'; -import jsonMap from 'json-source-map'; +import {parse} from '@mischnic/json-sourcemap'; import invariant from 'assert'; import nullthrows from 'nullthrows'; import { @@ -395,7 +395,7 @@ export class TargetResolver { let _pkgFilePath = (pkgFilePath = pkgFile.filePath); // For Flow pkgDir = path.dirname(_pkgFilePath); pkgContents = await this.fs.readFile(_pkgFilePath, 'utf8'); - pkgMap = jsonMap.parse(pkgContents.replace(/\t/g, ' ')); + pkgMap = parse(pkgContents, undefined, {tabWidth: 1}); let pp = toProjectPath(this.options.projectRoot, _pkgFilePath); this.api.invalidateOnFileUpdate(pp); diff --git a/packages/core/diagnostic/package.json b/packages/core/diagnostic/package.json index 40036f545e7..963023c6d11 100644 --- a/packages/core/diagnostic/package.json +++ b/packages/core/diagnostic/package.json @@ -24,7 +24,7 @@ "check-ts": "tsc --noEmit lib/diagnostic.d.ts" }, "dependencies": { - "json-source-map": "^0.6.1", + "@mischnic/json-sourcemap": "^0.1.0", "nullthrows": "^1.1.1" } } diff --git a/packages/core/diagnostic/src/diagnostic.js b/packages/core/diagnostic/src/diagnostic.js index c59f9884871..fe436925654 100644 --- a/packages/core/diagnostic/src/diagnostic.js +++ b/packages/core/diagnostic/src/diagnostic.js @@ -2,7 +2,7 @@ import invariant from 'assert'; import nullthrows from 'nullthrows'; -import jsonMap, {type Mapping} from 'json-source-map'; +import {parse, type Mapping} from '@mischnic/json-sourcemap'; /** These positions are 1-based (so 1 is the first line/column) */ export type DiagnosticHighlightLocation = {| @@ -231,9 +231,10 @@ export function generateJSONCodeHighlights( |}, ids: Array<{|key: string, type?: ?'key' | 'value', message?: string|}>, ): Array { - // json-source-map doesn't support a tabWidth option (yet) let map = - typeof data == 'string' ? jsonMap.parse(data.replace(/\t/g, ' ')) : data; + typeof data == 'string' + ? parse(data, undefined, {dialect: 'JSON5', tabWidth: 1}) + : data; return ids.map(({key, type, message}) => { let pos = nullthrows(map.pointers[key]); return { @@ -276,7 +277,7 @@ export function getJSONSourceLocation( /** Sanitizes object keys before using them as key in generateJSONCodeHighlights */ export function encodeJSONKeyComponent(component: string): string { - return component.replace(/\//g, '~1'); + return component.replace(/~/g, '~0').replace(/\//g, '~1'); } const escapeCharacters = ['\\', '*', '_', '~']; diff --git a/packages/core/utils/src/schema.js b/packages/core/utils/src/schema.js index 5004c13136a..b719a61c545 100644 --- a/packages/core/utils/src/schema.js +++ b/packages/core/utils/src/schema.js @@ -4,7 +4,7 @@ import ThrowableDiagnostic, { escapeMarkdown, encodeJSONKeyComponent, } from '@parcel/diagnostic'; -import type {Mapping} from 'json-source-map'; +import type {Mapping} from '@mischnic/json-sourcemap'; import nullthrows from 'nullthrows'; // flowlint-next-line untyped-import:off import levenshtein from 'fastest-levenshtein'; diff --git a/packages/transformers/babel/src/config.js b/packages/transformers/babel/src/config.js index 15f858400e0..66490264242 100644 --- a/packages/transformers/babel/src/config.js +++ b/packages/transformers/babel/src/config.js @@ -394,12 +394,7 @@ async function getCodeHighlights(fs, filePath, redundantPresets) { } if (pointers.length > 0) { - try { - return generateJSONCodeHighlights(contents, pointers); - } catch { - // TODO: support code highlights for json5 sources. - // Babel supports json5 syntax, but json-source-map does not. - } + return generateJSONCodeHighlights(contents, pointers); } } diff --git a/packages/transformers/webextension/package.json b/packages/transformers/webextension/package.json index 7fb077bbb05..e9b77772f1a 100644 --- a/packages/transformers/webextension/package.json +++ b/packages/transformers/webextension/package.json @@ -19,10 +19,10 @@ "parcel": "^2.4.1" }, "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", "@parcel/diagnostic": "2.4.1", "@parcel/plugin": "2.4.1", "@parcel/utils": "2.4.1", - "content-security-policy-parser": "^0.3.0", - "json-source-map": "^0.6.1" + "content-security-policy-parser": "^0.3.0" } } diff --git a/packages/transformers/webextension/src/WebExtensionTransformer.js b/packages/transformers/webextension/src/WebExtensionTransformer.js index c409b21450d..00d72df7efe 100644 --- a/packages/transformers/webextension/src/WebExtensionTransformer.js +++ b/packages/transformers/webextension/src/WebExtensionTransformer.js @@ -3,7 +3,7 @@ import type {MutableAsset} from '@parcel/types'; import {Transformer} from '@parcel/plugin'; import path from 'path'; -import jsm from 'json-source-map'; +import {parse} from '@mischnic/json-sourcemap'; import parseCSP from 'content-security-policy-parser'; import {validateSchema} from '@parcel/utils'; import ThrowableDiagnostic, { @@ -267,7 +267,7 @@ function cspPatchHMR(policy: ?string) { export default (new Transformer({ async transform({asset, options}) { const code = await asset.getCode(); - const parsed = jsm.parse(code); + const parsed = parse(code); const data: any = parsed.data; validateSchema.diagnostic( WebExtensionSchema, diff --git a/packages/transformers/webmanifest/package.json b/packages/transformers/webmanifest/package.json index 18be015c3ca..005b74800ef 100644 --- a/packages/transformers/webmanifest/package.json +++ b/packages/transformers/webmanifest/package.json @@ -19,9 +19,9 @@ "parcel": "^2.4.1" }, "dependencies": { + "@mischnic/json-sourcemap": "^0.1.0", "@parcel/diagnostic": "2.4.1", "@parcel/plugin": "2.4.1", - "@parcel/utils": "2.4.1", - "json-source-map": "^0.6.1" + "@parcel/utils": "2.4.1" } } diff --git a/packages/transformers/webmanifest/src/WebManifestTransformer.js b/packages/transformers/webmanifest/src/WebManifestTransformer.js index adefd04948c..138c90f3a76 100644 --- a/packages/transformers/webmanifest/src/WebManifestTransformer.js +++ b/packages/transformers/webmanifest/src/WebManifestTransformer.js @@ -3,7 +3,7 @@ import type {SchemaEntity} from '@parcel/utils'; import invariant from 'assert'; -import jsm from 'json-source-map'; +import {parse} from '@mischnic/json-sourcemap'; import {getJSONSourceLocation} from '@parcel/diagnostic'; import {Transformer} from '@parcel/plugin'; import {validateSchema} from '@parcel/utils'; @@ -36,7 +36,7 @@ const MANIFEST_SCHEMA: SchemaEntity = { export default (new Transformer({ async transform({asset}) { const source = await asset.getCode(); - const {data, pointers} = jsm.parse(source); + const {data, pointers} = parse(source); validateSchema.diagnostic( MANIFEST_SCHEMA, diff --git a/yarn.lock b/yarn.lock index bafcf838a20..f00b1a05290 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1853,6 +1853,18 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" +"@lezer/common@^0.15.0", "@lezer/common@^0.15.7": + version "0.15.12" + resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.12.tgz#2f21aec551dd5fd7d24eb069f90f54d5bc6ee5e9" + integrity sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig== + +"@lezer/lr@^0.15.4": + version "0.15.8" + resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-0.15.8.tgz#1564a911e62b0a0f75ca63794a6aa8c5dc63db21" + integrity sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg== + dependencies: + "@lezer/common" "^0.15.0" + "@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" @@ -1888,6 +1900,15 @@ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== +"@mischnic/json-sourcemap@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz#38af657be4108140a548638267d02a2ea3336507" + integrity sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA== + dependencies: + "@lezer/common" "^0.15.7" + "@lezer/lr" "^0.15.4" + json5 "^2.2.1" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -7998,11 +8019,6 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= -json-source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/json-source-map/-/json-source-map-0.6.1.tgz#e0b1f6f4ce13a9ad57e2ae165a24d06e62c79a0f" - integrity sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -8020,12 +8036,10 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +json5@^2.1.2, json5@^2.2.0, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonfile@^4.0.0: version "4.0.0" From 4910ce15c0b64c1a5c2f0d6bec683b4bac57e880 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:30:05 +0200 Subject: [PATCH 2/2] Add tests for JSON5 parcelrc and babelrc --- .../core/test/ParcelConfigRequest.test.js | 39 +++++++++++++ .../config-extends-not-found/.parcelrc-json5 | 3 + packages/core/diagnostic/src/diagnostic.js | 6 +- packages/core/integration-tests/test/babel.js | 56 +++++++++++++++++++ .../babel-warn-some-json5/.babelrc | 4 ++ .../babel-warn-some-json5/index.js | 0 .../babel-warn-some-json5/yarn.lock | 0 7 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 packages/core/core/test/fixtures/config-extends-not-found/.parcelrc-json5 create mode 100644 packages/core/integration-tests/test/integration/babel-warn-some-json5/.babelrc create mode 100644 packages/core/integration-tests/test/integration/babel-warn-some-json5/index.js create mode 100644 packages/core/integration-tests/test/integration/babel-warn-some-json5/yarn.lock diff --git a/packages/core/core/test/ParcelConfigRequest.test.js b/packages/core/core/test/ParcelConfigRequest.test.js index 8784a3d0143..f9496685773 100644 --- a/packages/core/core/test/ParcelConfigRequest.test.js +++ b/packages/core/core/test/ParcelConfigRequest.test.js @@ -813,6 +813,45 @@ describe('ParcelConfigRequest', () => { ); }); + it('should emit a codeframe when an extended parcel config file is not found in JSON5', async () => { + let configFilePath = path.join( + __dirname, + 'fixtures', + 'config-extends-not-found', + '.parcelrc-json5', + ); + let code = await DEFAULT_OPTIONS.inputFS.readFile(configFilePath, 'utf8'); + + // $FlowFixMe[prop-missing] + await assert.rejects( + () => parseAndProcessConfig(configFilePath, code, DEFAULT_OPTIONS), + { + name: 'Error', + diagnostics: [ + { + message: 'Cannot find extended parcel config', + origin: '@parcel/core', + codeFrames: [ + { + filePath: configFilePath, + language: 'json5', + code, + codeHighlights: [ + { + message: + '"./.parclrc-node-modules" does not exist, did you mean "./.parcelrc-node-modules"?', + start: {line: 2, column: 12}, + end: {line: 2, column: 36}, + }, + ], + }, + ], + }, + ], + }, + ); + }); + it('should emit a codeframe when an extended parcel config node module is not found', async () => { let configFilePath = path.join( __dirname, diff --git a/packages/core/core/test/fixtures/config-extends-not-found/.parcelrc-json5 b/packages/core/core/test/fixtures/config-extends-not-found/.parcelrc-json5 new file mode 100644 index 00000000000..03464dc7fc8 --- /dev/null +++ b/packages/core/core/test/fixtures/config-extends-not-found/.parcelrc-json5 @@ -0,0 +1,3 @@ +{ + extends: "./.parclrc-node-modules" +} diff --git a/packages/core/diagnostic/src/diagnostic.js b/packages/core/diagnostic/src/diagnostic.js index fe436925654..8ab4c398147 100644 --- a/packages/core/diagnostic/src/diagnostic.js +++ b/packages/core/diagnostic/src/diagnostic.js @@ -215,8 +215,8 @@ export default class ThrowableDiagnostic extends Error { } /** - * Turns a list of positions in a JSON file with messages into a list of diagnostics. - * Uses epoberezkin/json-source-map. + * Turns a list of positions in a JSON5 file with messages into a list of diagnostics. + * Uses @mischnic/json-sourcemap. * * @param code the JSON code * @param ids A list of JSON keypaths (key: "/some/parent/child") with corresponding messages, \ @@ -245,7 +245,7 @@ export function generateJSONCodeHighlights( } /** - * Converts entries in epoberezkin/json-source-map's + * Converts entries in @mischnic/json-sourcemap's * result.pointers array. */ export function getJSONSourceLocation( diff --git a/packages/core/integration-tests/test/babel.js b/packages/core/integration-tests/test/babel.js index cb664a1b979..cbd333f9cf3 100644 --- a/packages/core/integration-tests/test/babel.js +++ b/packages/core/integration-tests/test/babel.js @@ -711,4 +711,60 @@ describe('babel', function () { }, ]); }); + + it('should warn when a JSON5 babel config contains redundant plugins', async function () { + let messages = []; + let loggerDisposable = Logger.onLog(message => { + messages.push(message); + }); + let filePath = path.join( + __dirname, + '/integration/babel-warn-some-json5/index.js', + ); + await bundle(filePath); + loggerDisposable.dispose(); + + let babelrcPath = path.resolve(path.dirname(filePath), '.babelrc'); + assert.deepEqual(messages, [ + { + type: 'log', + level: 'warn', + diagnostics: [ + { + origin: '@parcel/transformer-babel', + message: md`Parcel includes transpilation by default. Babel config __${path.relative( + process.cwd(), + babelrcPath, + )}__ includes the following redundant presets: __@parcel/babel-preset-env__. Removing these may improve build performance.`, + codeFrames: [ + { + filePath: babelrcPath, + codeHighlights: [ + { + message: undefined, + start: { + line: 2, + column: 13, + }, + end: { + line: 2, + column: 38, + }, + }, + ], + }, + ], + hints: [ + md`Remove the above presets from __${path.relative( + process.cwd(), + babelrcPath, + )}__`, + ], + documentationURL: + 'https://parceljs.org/languages/javascript/#default-presets', + }, + ], + }, + ]); + }); }); diff --git a/packages/core/integration-tests/test/integration/babel-warn-some-json5/.babelrc b/packages/core/integration-tests/test/integration/babel-warn-some-json5/.babelrc new file mode 100644 index 00000000000..3da8adeca2a --- /dev/null +++ b/packages/core/integration-tests/test/integration/babel-warn-some-json5/.babelrc @@ -0,0 +1,4 @@ +{ + presets: ["@parcel/babel-preset-env"], + "plugins": ["../babelrc-custom/babel-plugin-dummy"] +} diff --git a/packages/core/integration-tests/test/integration/babel-warn-some-json5/index.js b/packages/core/integration-tests/test/integration/babel-warn-some-json5/index.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/babel-warn-some-json5/yarn.lock b/packages/core/integration-tests/test/integration/babel-warn-some-json5/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d