rsnext/packages/next/build/webpack/plugins/webpack-conformance-plugin/index.ts
Prateek Bhatnagar 16672a4353
Adding conformance checks (#10314)
* adding tests  for rect sync conformance check

* adding test for react sync script conformance check

* reverting yarn lock changes

* adding duplicate polyfill conformance

* bug fixes in dulpicate polyfill conformance check

* adding settings capability to conformance plugin

* removing minification check from server build

* bug fix

* settings for react sync script check
2020-03-02 22:53:18 +01:00

151 lines
4.6 KiB
TypeScript

import { Compiler, compilation } from 'webpack'
import {
IConformanceTestResult,
IWebpackConformanceTest,
IConformanceAnomaly,
IGetAstNodeResult,
NodeInspector,
IConformanceTestStatus,
} from './TestInterface'
import { NodePath } from 'ast-types/lib/node-path'
import { visit } from 'recast'
export { MinificationConformanceCheck } from './checks/minification-conformance-check'
export { ReactSyncScriptsConformanceCheck } from './checks/react-sync-scripts-conformance-check'
export { DuplicatePolyfillsConformanceCheck } from './checks/duplicate-polyfills-conformance-check'
export interface IWebpackConformancePluginOptions {
tests: IWebpackConformanceTest[]
}
interface VisitorMap {
[key: string]: (path: NodePath) => void
}
export default class WebpackConformancePlugin {
private tests: IWebpackConformanceTest[]
private errors: Array<IConformanceAnomaly>
private warnings: Array<IConformanceAnomaly>
private compiler?: Compiler
constructor(options: IWebpackConformancePluginOptions) {
this.tests = []
if (options.tests) {
this.tests.push(...options.tests)
}
this.errors = []
this.warnings = []
}
private gatherResults(results: Array<IConformanceTestResult>): void {
results.forEach(result => {
if (result.result === IConformanceTestStatus.FAILED) {
result.errors && this.errors.push(...result.errors)
result.warnings && this.warnings.push(...result.warnings)
}
})
}
private buildStartedHandler = (
compilation: compilation.Compilation,
callback: () => void
) => {
const buildStartedResults: IConformanceTestResult[] = this.tests.map(
test => {
if (test.buildStared && this.compiler) {
return test.buildStared(this.compiler.options)
}
return {
result: IConformanceTestStatus.SUCCESS,
} as IConformanceTestResult
}
)
this.gatherResults(buildStartedResults)
callback()
}
private buildCompletedHandler = (
compilation: compilation.Compilation,
cb: () => void
): void => {
const buildCompletedResults: IConformanceTestResult[] = this.tests.map(
test => {
if (test.buildCompleted) {
return test.buildCompleted(compilation.assets)
}
return {
result: IConformanceTestStatus.SUCCESS,
} as IConformanceTestResult
}
)
this.gatherResults(buildCompletedResults)
compilation.errors.push(...this.errors)
compilation.warnings.push(...this.warnings)
cb()
}
private parserHandler = (factory: compilation.NormalModuleFactory): void => {
const JS_TYPES = ['auto', 'esm', 'dynamic']
const collectedVisitors: Map<string, [NodeInspector?]> = new Map()
// Collect all interested visitors from all tests.
this.tests.forEach(test => {
if (test.getAstNode) {
const getAstNodeCallbacks: IGetAstNodeResult[] = test.getAstNode()
getAstNodeCallbacks.forEach(result => {
if (!collectedVisitors.has(result.visitor)) {
collectedVisitors.set(result.visitor, [])
}
;(collectedVisitors.get(result.visitor) as NodeInspector[]).push(
result.inspectNode
)
})
}
})
// Do an extra walk per module and add interested visitors to the walk.
for (const type of JS_TYPES) {
factory.hooks.parser
.for('javascript/' + type)
.tap(this.constructor.name, parser => {
parser.hooks.program.tap(this.constructor.name, (ast: any) => {
const visitors: VisitorMap = {}
const that = this
for (const visitorKey of collectedVisitors.keys()) {
visitors[visitorKey] = function(path: NodePath) {
const callbacks = collectedVisitors.get(visitorKey) || []
callbacks.forEach(cb => {
if (!cb) {
return
}
const { request } = parser.state.module
const outcome = cb(path, { request })
that.gatherResults([outcome])
})
this.traverse(path)
return false
}
}
visit(ast, visitors)
})
})
}
}
public apply(compiler: Compiler) {
this.compiler = compiler
compiler.hooks.make.tapAsync(
this.constructor.name,
this.buildStartedHandler
)
compiler.hooks.emit.tapAsync(
this.constructor.name,
this.buildCompletedHandler
)
compiler.hooks.normalModuleFactory.tap(
this.constructor.name,
this.parserHandler
)
}
}