From fe6158e5c728398bc618371dfbc12c6740ac9cc1 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sun, 31 Jul 2022 09:07:07 -0700 Subject: [PATCH] Log resolved targets in verbose log level (#8254) --- packages/core/codeframe/src/codeframe.js | 24 +- .../core/core/src/requests/TargetRequest.js | 429 ++++++++++++++++-- packages/reporters/cli/src/CLIReporter.js | 7 +- .../reporters/cli/test/CLIReporter.test.js | 4 +- 4 files changed, 420 insertions(+), 44 deletions(-) diff --git a/packages/core/codeframe/src/codeframe.js b/packages/core/codeframe/src/codeframe.js index 3eca8729395..53dc5119796 100644 --- a/packages/core/codeframe/src/codeframe.js +++ b/packages/core/codeframe/src/codeframe.js @@ -84,6 +84,7 @@ export default function codeFrame( }; // Make columns/lines start at 1 + let originalHighlights = highlights; highlights = highlights.map(h => { return { start: { @@ -112,10 +113,17 @@ export default function codeFrame( let startLine = firstHighlight.start.line - opts.padding.before; startLine = startLine < 0 ? 0 : startLine; let endLineIndex = lastHighlight.end.line + opts.padding.after; - endLineIndex = - endLineIndex - startLine > opts.maxLines - ? startLine + opts.maxLines - 1 - : endLineIndex; + let tail; + if (endLineIndex - startLine > opts.maxLines) { + let maxLine = startLine + opts.maxLines - 1; + highlights = highlights.filter(h => h.start.line < maxLine); + lastHighlight = highlights[0]; + endLineIndex = Math.min( + maxLine, + lastHighlight.end.line + opts.padding.after, + ); + tail = originalHighlights.filter(h => h.start.line > endLineIndex); + } let lineNumberLength = (endLineIndex + 1).toString(10).length; @@ -281,5 +289,11 @@ export default function codeFrame( } } - return resultLines.join('\n'); + let result = resultLines.join('\n'); + + if (tail && tail.length > 0) { + result += '\n\n' + codeFrame(code, tail, inputOpts); + } + + return result; } diff --git a/packages/core/core/src/requests/TargetRequest.js b/packages/core/core/src/requests/TargetRequest.js index eb0a6339910..89fff965c56 100644 --- a/packages/core/core/src/requests/TargetRequest.js +++ b/packages/core/core/src/requests/TargetRequest.js @@ -28,6 +28,7 @@ import { hashObject, validateSchema, } from '@parcel/utils'; +import logger from '@parcel/logger'; import {createEnvironment} from '../Environment'; import createParcelConfigRequest, { getCachedParcelConfig, @@ -142,18 +143,54 @@ async function run({input, api, options}: RunOpts) { } } + if (options.logLevel === 'verbose') { + await debugResolvedTargets( + input, + targets, + targetResolver.targetInfo, + options, + ); + } + return targets; } +type TargetInfo = {| + output: TargetKeyInfo, + engines: TargetKeyInfo, + context: TargetKeyInfo, + includeNodeModules: TargetKeyInfo, + outputFormat: TargetKeyInfo, + isLibrary: TargetKeyInfo, + shouldOptimize: TargetKeyInfo, + shouldScopeHoist: TargetKeyInfo, +|}; + +type TargetKeyInfo = + | {| + path: string, + type?: 'key' | 'value', + |} + | {| + inferred: string, + type?: 'key' | 'value', + message: string, + |} + | {| + message: string, + |}; + export class TargetResolver { fs: FileSystem; api: RunAPI; options: ParcelOptions; + targetInfo: Map; constructor(api: RunAPI, options: ParcelOptions) { this.api = api; this.fs = options.inputFS; this.options = options; + this.targetInfo = new Map(); } async resolve( @@ -414,6 +451,8 @@ export class TargetResolver { '/engines', 'Invalid engines in package.json', ) || {}; + let browsersLoc = {path: '/engines/browsers'}; + let nodeLoc = {path: '/engines/node'}; if (pkgEngines.browsers == null) { let env = this.options.env.BROWSERSLIST_ENV ?? @@ -431,6 +470,8 @@ export class TargetResolver { ...pkgEngines, browsers: browserslist, }; + + browsersLoc = {path: '/browserslist'}; } else { let browserslistConfig = await resolveConfig( this.fs, @@ -453,16 +494,23 @@ export class TargetResolver { let contents = await this.fs.readFile(browserslistConfig, 'utf8'); let config = browserslist.parseConfig(contents); let browserslistBrowsers = config[env] || config.defaults; + let pp = toProjectPath(this.options.projectRoot, browserslistConfig); if (browserslistBrowsers?.length > 0) { pkgEngines = { ...pkgEngines, browsers: browserslistBrowsers, }; + + browsersLoc = { + message: `(defined in ${path.relative( + process.cwd(), + browserslistConfig, + )})`, + }; } // Invalidate whenever browserslist config file or relevant environment variables change - let pp = toProjectPath(this.options.projectRoot, browserslistConfig); this.api.invalidateOnFileUpdate(pp); this.api.invalidateOnFileDelete(pp); this.api.invalidateOnEnvChange('BROWSERSLIST_ENV'); @@ -475,15 +523,6 @@ export class TargetResolver { let node = pkgEngines.node; let browsers = pkgEngines.browsers; - // If there is a separate `browser` target, or an `engines.node` field but no browser targets, then - // the `main` and `module` targets refer to node, otherwise browser. - let mainContext = - pkg.browser ?? pkgTargets.browser ?? (node != null && browsers == null) - ? 'node' - : 'browser'; - let moduleContext = - pkg.browser ?? pkgTargets.browser ? 'browser' : mainContext; - let defaultEngines = this.options.defaultTargetOptions.engines; let context = browsers ?? node == null ? 'browser' : 'node'; if (context === 'browser' && pkgEngines.browsers == null) { @@ -491,13 +530,93 @@ export class TargetResolver { ...pkgEngines, browsers: defaultEngines?.browsers ?? DEFAULT_ENGINES.browsers, }; + browsersLoc = {message: '(default)'}; } else if (context === 'node' && pkgEngines.node == null) { pkgEngines = { ...pkgEngines, node: defaultEngines?.node ?? DEFAULT_ENGINES.node, }; + nodeLoc = {message: '(default)'}; } + // If there is a separate `browser` target, or an `engines.node` field but no browser targets, then + // the `main` and `module` targets refer to node, otherwise browser. + let mainContext = + pkg.browser ?? pkgTargets.browser ?? (node != null && browsers == null) + ? 'node' + : 'browser'; + let mainContextLoc: TargetKeyInfo = + pkg.browser != null + ? { + inferred: '/browser', + message: '(because a browser field also exists)', + type: 'key', + } + : pkgTargets.browser + ? { + inferred: '/targets/browser', + message: '(because a browser target also exists)', + type: 'key', + } + : node != null && browsers == null + ? nodeLoc.path + ? { + inferred: nodeLoc.path, + message: '(because node engines were defined)', + type: 'key', + } + : nodeLoc + : {message: '(default)'}; + let moduleContext = + pkg.browser ?? pkgTargets.browser ? 'browser' : mainContext; + let moduleContextLoc: TargetKeyInfo = + pkg.browser != null + ? { + inferred: '/browser', + message: '(because a browser field also exists)', + type: 'key', + } + : pkgTargets.browser + ? { + inferred: '/targets/browser', + message: '(becausea browser target also exists)', + type: 'key', + } + : mainContextLoc; + + let getEnginesLoc = (targetName, descriptor): TargetKeyInfo => { + let enginesLoc = `/targets/${targetName}/engines`; + switch (context) { + case 'browser': + case 'web-worker': + case 'service-worker': + case 'worklet': { + if (descriptor.engines) { + return {path: enginesLoc + '/browsers'}; + } else { + return browsersLoc; + } + } + case 'node': { + if (descriptor.engines) { + return {path: enginesLoc + '/node'}; + } else { + return nodeLoc; + } + } + case 'electron-main': + case 'electron-renderer': { + if (descriptor.engines?.electron != null) { + return {path: enginesLoc + '/electron'}; + } else if (pkgEngines?.electron != null) { + return {path: '/engines/electron'}; + } + } + } + + return {message: '(default)'}; + }; + for (let targetName in COMMON_TARGETS) { let _targetDist; let pointer; @@ -634,14 +753,15 @@ export class TargetResolver { }); } - let inferredOutputFormat = this.inferOutputFormat( - distEntry, - descriptor, - targetName, - pkg, - pkgFilePath, - pkgContents, - ); + let [inferredOutputFormat, inferredOutputFormatField] = + this.inferOutputFormat( + distEntry, + descriptor, + targetName, + pkg, + pkgFilePath, + pkgContents, + ); let outputFormat = descriptor.outputFormat ?? @@ -725,6 +845,14 @@ export class TargetResolver { }); } + let context = + descriptor.context ?? + (targetName === 'browser' + ? 'browser' + : isModule + ? moduleContext + : mainContext); + targets.set(targetName, { name: targetName, distDir, @@ -733,13 +861,7 @@ export class TargetResolver { descriptor.publicUrl ?? this.options.defaultTargetOptions.publicUrl, env: createEnvironment({ engines: descriptor.engines ?? pkgEngines, - context: - descriptor.context ?? - (targetName === 'browser' - ? 'browser' - : isModule - ? moduleContext - : mainContext), + context, includeNodeModules: descriptor.includeNodeModules ?? false, outputFormat, isLibrary: true, @@ -751,6 +873,43 @@ export class TargetResolver { }), loc: toInternalSourceLocation(this.options.projectRoot, loc), }); + + this.targetInfo.set(targetName, { + output: {path: pointer}, + engines: getEnginesLoc(targetName, descriptor), + context: descriptor.context + ? {path: `/targets/${targetName}/context`} + : targetName === 'browser' + ? { + message: '(inferred from target name)', + inferred: pointer, + type: 'key', + } + : isModule + ? moduleContextLoc + : mainContextLoc, + includeNodeModules: descriptor.includeNodeModules + ? {path: `/targets/${targetName}/includeNodeModules`, type: 'key'} + : {message: '(default)'}, + outputFormat: descriptor.outputFormat + ? {path: `/targets/${targetName}/outputFormat`} + : inferredOutputFormatField === '/type' + ? { + message: `(inferred from package.json#type)`, + inferred: inferredOutputFormatField, + } + : inferredOutputFormatField != null + ? { + message: `(inferred from file extension)`, + inferred: inferredOutputFormatField, + } + : {message: '(default)'}, + isLibrary: {message: '(default)'}, + shouldOptimize: descriptor.optimize + ? {path: `/targets/${targetName}/optimize`} + : {message: '(default)'}, + shouldScopeHoist: {message: '(default)'}, + }); } } @@ -764,6 +923,7 @@ export class TargetResolver { let distDir; let distEntry; let loc; + let pointer; if (distPath == null) { distDir = fromProjectPath( @@ -810,6 +970,7 @@ export class TargetResolver { filePath: pkgFilePath, ...getJSONSourceLocation(pkgMap.pointers[`/${targetName}`], 'value'), }; + pointer = `/${targetName}`; } if (targetName in pkgTargets) { @@ -825,14 +986,15 @@ export class TargetResolver { continue; } - let inferredOutputFormat = this.inferOutputFormat( - distEntry, - descriptor, - targetName, - pkg, - pkgFilePath, - pkgContents, - ); + let [inferredOutputFormat, inferredOutputFormatField] = + this.inferOutputFormat( + distEntry, + descriptor, + targetName, + pkg, + pkgFilePath, + pkgContents, + ); if (descriptor.scopeHoist === false && descriptor.isLibrary) { let contents: string = @@ -909,6 +1071,42 @@ export class TargetResolver { }), loc: toInternalSourceLocation(this.options.projectRoot, loc), }); + + this.targetInfo.set(targetName, { + output: pointer != null ? {path: pointer} : {message: '(default)'}, + engines: getEnginesLoc(targetName, descriptor), + context: descriptor.context + ? {path: `/targets/${targetName}/context`} + : {message: '(default)'}, + includeNodeModules: descriptor.includeNodeModules + ? {path: `/targets/${targetName}/includeNodeModules`, type: 'key'} + : {message: '(default)'}, + outputFormat: descriptor.outputFormat + ? {path: `/targets/${targetName}/outputFormat`} + : inferredOutputFormatField === '/type' + ? { + message: `(inferred from package.json#type)`, + inferred: inferredOutputFormatField, + } + : inferredOutputFormatField != null + ? { + message: `(inferred from file extension)`, + inferred: inferredOutputFormatField, + } + : {message: '(default)'}, + isLibrary: + descriptor.isLibrary != null + ? {path: `/targets/${targetName}/isLibrary`} + : {message: '(default)'}, + shouldOptimize: + descriptor.optimize != null + ? {path: `/targets/${targetName}/optimize`} + : {message: '(default)'}, + shouldScopeHoist: + descriptor.scopeHoist != null + ? {path: `/targets/${targetName}/scopeHoist`} + : {message: '(default)'}, + }); } } @@ -952,7 +1150,7 @@ export class TargetResolver { pkg: PackageJSON, pkgFilePath: ?FilePath, pkgContents: ?string, - ): ?OutputFormat { + ): [?OutputFormat, ?string] { // Infer the outputFormat based on package.json properties. // If the extension is .mjs it's always a module. // If the extension is .cjs, it's always commonjs. @@ -1036,7 +1234,7 @@ export class TargetResolver { }); } - return inferredOutputFormat; + return [inferredOutputFormat, inferredOutputFormatField]; } } @@ -1263,3 +1461,164 @@ function assertTargetsAreNotEntries( } } } + +async function debugResolvedTargets(input, targets, targetInfo, options) { + for (let target of targets) { + let info = targetInfo.get(target.name); + let loc = target.loc; + if (!loc || !info) { + continue; + } + + let output = fromProjectPath(options.projectRoot, target.distDir); + if (target.distEntry != null) { + output = path.join(output, target.distEntry); + } + + // Resolve relevant engines for context. + let engines; + switch (target.env.context) { + case 'browser': + case 'web-worker': + case 'service-worker': + case 'worklet': { + let browsers = target.env.engines.browsers; + engines = Array.isArray(browsers) ? browsers.join(', ') : browsers; + break; + } + case 'node': + engines = target.env.engines.node; + break; + case 'electron-main': + case 'electron-renderer': + engines = target.env.engines.electron; + break; + } + + let highlights = []; + if (input.loc) { + highlights.push({ + start: input.loc.start, + end: input.loc.end, + message: 'entry defined here', + }); + } + + // Read package.json where target is defined. + let targetFilePath = fromProjectPath(options.projectRoot, loc.filePath); + let contents = await options.inputFS.readFile(targetFilePath, 'utf8'); + + // Builds up map of code highlights for each defined/inferred path in the package.json. + let jsonHighlights = new Map(); + for (let key in info) { + let keyInfo = info[key]; + let path = keyInfo.path || keyInfo.inferred; + if (!path) { + continue; + } + + let type = keyInfo.type || 'value'; + let highlight = jsonHighlights.get(path); + if (!highlight) { + highlight = { + type: type, + defined: '', + inferred: [], + }; + jsonHighlights.set(path, highlight); + } else if (highlight.type !== type) { + highlight.type = null; + } + + if (keyInfo.path) { + highlight.defined = md`${key} defined here`; + } + + if (keyInfo.inferred) { + highlight.inferred.push( + md`${key} to be ${JSON.stringify(target.env[key])}`, + ); + } + } + + // $FlowFixMe + let listFormat = new Intl.ListFormat('en-US'); + + // Generate human friendly messages for each field. + let highlightsWithMessages = [...jsonHighlights].map(([k, v]) => { + let message = v.defined; + if (v.inferred.length > 0) { + message += (message ? ', ' : '') + 'caused '; + message += listFormat.format(v.inferred); + } + + return { + key: k, + type: v.type, + message, + }; + }); + + // Get code highlights from JSON paths. + highlights.push( + ...generateJSONCodeHighlights(contents, highlightsWithMessages), + ); + + // Format includeNodeModules to be human readable. + let includeNodeModules; + if (typeof target.env.includeNodeModules === 'boolean') { + includeNodeModules = String(target.env.includeNodeModules); + } else if (Array.isArray(target.env.includeNodeModules)) { + includeNodeModules = + 'only ' + + listFormat.format( + target.env.includeNodeModules.map(m => JSON.stringify(m)), + ); + } else if ( + target.env.includeNodeModules && + typeof target.env.includeNodeModules === 'object' + ) { + includeNodeModules = + 'all except ' + + listFormat.format( + Object.entries(target.env.includeNodeModules) + .filter(([, v]) => v === false) + .map(([k]) => JSON.stringify(k)), + ); + } + + let format = v => (v.message != null ? md.italic(v.message) : ''); + logger.verbose({ + origin: '@parcel/core', + message: md`**Target** "${target.name}" + + **Entry**: ${path.relative( + process.cwd(), + fromProjectPath(options.projectRoot, input.filePath), + )} + **Output**: ${path.relative(process.cwd(), output)} + **Format**: ${target.env.outputFormat} ${format( + info.outputFormat, + )} + **Context**: ${target.env.context} ${format(info.context)} + **Engines**: ${engines || ''} ${format(info.engines)} + **Library Mode**: ${String(target.env.isLibrary)} ${format( + info.isLibrary, + )} +**Include Node Modules**: ${includeNodeModules} ${format( + info.includeNodeModules, + )} + **Optimize**: ${String(target.env.shouldOptimize)} ${format( + info.shouldOptimize, + )}`, + codeFrames: target.loc + ? [ + { + filePath: targetFilePath, + codeHighlights: highlights, + }, + ] + : [], + }); + } +} diff --git a/packages/reporters/cli/src/CLIReporter.js b/packages/reporters/cli/src/CLIReporter.js index 9bd9d77be93..ab0c0eb7ace 100644 --- a/packages/reporters/cli/src/CLIReporter.js +++ b/packages/reporters/cli/src/CLIReporter.js @@ -170,13 +170,14 @@ async function writeDiagnostic( ) { let columns = getTerminalWidth().columns; let indent = 2; + let spaceAfter = isError; for (let diagnostic of diagnostics) { let {message, stack, codeframe, hints, documentation} = await prettyDiagnostic(diagnostic, options, columns - indent); // $FlowFixMe[incompatible-use] message = chalk[color](message); - if (isError) { + if (spaceAfter) { writeOut(''); } @@ -221,9 +222,11 @@ async function writeDiagnostic( ), ); } + + spaceAfter = stack || codeframe || hints.length > 0 || documentation; } - if (isError) { + if (spaceAfter) { writeOut(''); } } diff --git a/packages/reporters/cli/test/CLIReporter.test.js b/packages/reporters/cli/test/CLIReporter.test.js index 317f8975186..4e2e9c033b4 100644 --- a/packages/reporters/cli/test/CLIReporter.test.js +++ b/packages/reporters/cli/test/CLIReporter.test.js @@ -133,7 +133,7 @@ describe('CLIReporter', () => { EMPTY_OPTIONS, ); - assert.equal(stdoutOutput, '\n\n\n\n'); + assert.equal(stdoutOutput, '\n\n'); assert.equal(stderrOutput, 'test: error\ntest: warn\n'); }); @@ -165,7 +165,7 @@ describe('CLIReporter', () => { EMPTY_OPTIONS, ); - assert.equal(stdoutOutput, '\n\n\n\n'); + assert.equal(stdoutOutput, '\n\n'); assert(stderrOutput.includes('test: error\n')); assert(stderrOutput.includes('test: warn\n')); });