UNPKG

@parcel/core

Version:
223 lines (205 loc) • 7.32 kB
// @flow strict-local import type {WorkerApi} from '@parcel/workers'; import type {AssetGroup, ParcelOptions, ReportFn} from './types'; import type {Validator, ValidateResult} from '@parcel/types'; import type {Diagnostic} from '@parcel/diagnostic'; import path from 'path'; import {resolveConfig} from '@parcel/utils'; import logger, {PluginLogger} from '@parcel/logger'; import ThrowableDiagnostic, {errorToDiagnostic} from '@parcel/diagnostic'; import ParcelConfig from './ParcelConfig'; import UncommittedAsset from './UncommittedAsset'; import {createAsset} from './assetUtils'; import {Asset} from './public/Asset'; import PluginOptions from './public/PluginOptions'; import summarizeRequest from './summarizeRequest'; import {fromProjectPath, fromProjectPathRelative} from './projectPath'; import {PluginTracer} from '@parcel/profiler'; export type ValidationOpts = {| config: ParcelConfig, /** * If true, this Validation instance will run all validators that implement the single-threaded "validateAll" method. * If falsy, it will run validators that implement the one-asset-at-a-time "validate" method. */ dedicatedThread?: boolean, options: ParcelOptions, requests: AssetGroup[], report: ReportFn, workerApi?: WorkerApi, |}; export default class Validation { allAssets: {[validatorName: string]: UncommittedAsset[], ...} = {}; allValidators: {[validatorName: string]: Validator, ...} = {}; dedicatedThread: boolean; impactfulOptions: $Shape<ParcelOptions>; options: ParcelOptions; parcelConfig: ParcelConfig; report: ReportFn; requests: AssetGroup[]; workerApi: ?WorkerApi; constructor({ config, dedicatedThread, options, requests, report, workerApi, }: ValidationOpts) { this.dedicatedThread = dedicatedThread ?? false; this.options = options; this.parcelConfig = config; this.report = report; this.requests = requests; this.workerApi = workerApi; } async run(): Promise<void> { let pluginOptions = new PluginOptions(this.options); await this.buildAssetsAndValidators(); await Promise.all( Object.keys(this.allValidators).map(async validatorName => { let assets = this.allAssets[validatorName]; if (assets) { let plugin = this.allValidators[validatorName]; let validatorLogger = new PluginLogger({origin: validatorName}); let validatorTracer = new PluginTracer({ origin: validatorName, category: 'validator', }); let validatorResults: Array<?ValidateResult> = []; try { // If the plugin supports the single-threading validateAll method, pass all assets to it. if (plugin.validateAll && this.dedicatedThread) { validatorResults = await plugin.validateAll({ assets: assets.map(asset => new Asset(asset)), options: pluginOptions, logger: validatorLogger, tracer: validatorTracer, resolveConfigWithPath: ( configNames: Array<string>, assetFilePath: string, ) => resolveConfig( this.options.inputFS, assetFilePath, configNames, this.options.projectRoot, ), }); } // Otherwise, pass the assets one-at-a-time else if (plugin.validate && !this.dedicatedThread) { await Promise.all( assets.map(async input => { let config = null; let publicAsset = new Asset(input); if (plugin.getConfig) { config = await plugin.getConfig({ asset: publicAsset, options: pluginOptions, logger: validatorLogger, tracer: validatorTracer, resolveConfig: (configNames: Array<string>) => resolveConfig( this.options.inputFS, publicAsset.filePath, configNames, this.options.projectRoot, ), }); } let validatorResult = await plugin.validate({ asset: publicAsset, options: pluginOptions, config, logger: validatorLogger, tracer: validatorTracer, }); validatorResults.push(validatorResult); }), ); } this.handleResults(validatorResults); } catch (e) { throw new ThrowableDiagnostic({ diagnostic: errorToDiagnostic(e, { origin: validatorName, }), }); } } }), ); } async buildAssetsAndValidators() { // Figure out what validators need to be run, and group the assets by the relevant validators. await Promise.all( this.requests.map(async request => { this.report({ type: 'validation', filePath: fromProjectPath(this.options.projectRoot, request.filePath), }); let asset = await this.loadAsset(request); let validators = await this.parcelConfig.getValidators( request.filePath, ); for (let validator of validators) { this.allValidators[validator.name] = validator.plugin; if (this.allAssets[validator.name]) { this.allAssets[validator.name].push(asset); } else { this.allAssets[validator.name] = [asset]; } } }), ); } handleResults(validatorResults: Array<?ValidateResult>) { let warnings: Array<Diagnostic> = []; let errors: Array<Diagnostic> = []; validatorResults.forEach(result => { if (result) { warnings.push(...result.warnings); errors.push(...result.errors); } }); if (errors.length > 0) { throw new ThrowableDiagnostic({ diagnostic: errors, }); } if (warnings.length > 0) { logger.warn(warnings); } } async loadAsset(request: AssetGroup): Promise<UncommittedAsset> { let {filePath, env, code, sideEffects, query} = request; let {content, size, hash, isSource} = await summarizeRequest( this.options.inputFS, { filePath: fromProjectPath(this.options.projectRoot, request.filePath), }, ); // If the transformer request passed code rather than a filename, // use a hash as the base for the id to ensure it is unique. let idBase = code != null ? hash : fromProjectPathRelative(filePath); return new UncommittedAsset({ idBase, value: createAsset(this.options.projectRoot, { idBase, filePath: filePath, isSource, type: path.extname(fromProjectPathRelative(filePath)).slice(1), hash, query, env: env, stats: { time: 0, size, }, sideEffects: sideEffects, }), options: this.options, content, }); } }