diff --git a/cli/help.md b/cli/help.md index b6b853dc50b..a6f24792880 100644 --- a/cli/help.md +++ b/cli/help.md @@ -51,6 +51,7 @@ Basic options: --preserveModules Preserve module structure --preserveModulesRoot Put preserved modules under this path at root level --preserveSymlinks Do not follow symlinks when resolving files +--no-sanitizeFileName Do not replace invalid characters in file names --shimMissingExports Create shim variables for missing exports --silent Don't print warnings --sourcemapExcludeSources Do not include source code in source maps diff --git a/docs/01-command-line-reference.md b/docs/01-command-line-reference.md index f3438bf71c1..659e7f0ba42 100755 --- a/docs/01-command-line-reference.md +++ b/docs/01-command-line-reference.md @@ -95,6 +95,7 @@ export default { // can be an array (for multiple inputs) namespaceToStringTag, noConflict, preferConst, + sanitizeFileName, strict, systemNullSetters }, @@ -310,6 +311,7 @@ Many options have command line equivalents. In those cases, any arguments passed --preserveModules Preserve module structure --preserveModulesRoot Put preserved modules under this path at root level --preserveSymlinks Do not follow symlinks when resolving files +--no-sanitizeFileName Do not replace invalid characters in file names --shimMissingExports Create shim variables for missing exports --silent Don't print warnings --sourcemapExcludeSources Do not include source code in source maps diff --git a/docs/02-javascript-api.md b/docs/02-javascript-api.md index 93253d4260a..725e2f5d5d7 100755 --- a/docs/02-javascript-api.md +++ b/docs/02-javascript-api.md @@ -161,6 +161,7 @@ const outputOptions = { namespaceToStringTag, noConflict, preferConst, + sanitizeFileName, strict, systemNullSetters }; diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index 4e8c7d97432..76e730ed3d6 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -1320,6 +1320,15 @@ Default: `false` Generate `const` declarations for exports rather than `var` declarations. +#### output.sanitizeFileName +Type: `boolean | (string) => string`
+CLI: `--sanitizeFileName`/`no-sanitizeFileName` +Default: `true` + +Set to `false` to disable all chunk name sanitizations (removal of `\0`, `?` and `*` characters). + +Alternatively set to a function to allow custom chunk name sanitization. + #### output.strict Type: `boolean`
CLI: `--strict`/`--no-strict`
diff --git a/src/Bundle.ts b/src/Bundle.ts index 65f97532c52..01e731fa2a7 100644 --- a/src/Bundle.ts +++ b/src/Bundle.ts @@ -43,7 +43,7 @@ export default class Bundle { const outputBundle: OutputBundleWithPlaceholders = Object.create(null); this.pluginDriver.setOutputBundle( outputBundle, - this.outputOptions.assetFileNames, + this.outputOptions, this.facadeChunkByModule ); try { diff --git a/src/Chunk.ts b/src/Chunk.ts index 0d79ffb1811..57103cdfcbf 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -56,7 +56,6 @@ import relativeId, { getAliasName } from './utils/relativeId'; import renderChunk from './utils/renderChunk'; import { RenderOptions } from './utils/renderHelpers'; import { makeUnique, renderNamePattern } from './utils/renderNamePattern'; -import { sanitizeFileName } from './utils/sanitizeFileName'; import { timeEnd, timeStart } from './utils/timers'; import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames'; @@ -437,7 +436,7 @@ export default class Chunk { unsetOptions: Set ): string { const id = this.orderedModules[0].id; - const sanitizedId = sanitizeFileName(id); + const sanitizedId = this.outputOptions.sanitizeFileName(id); let path: string; if (isAbsolute(id)) { const extension = extname(id); @@ -501,7 +500,7 @@ export default class Chunk { } getChunkName(): string { - return this.name || (this.name = sanitizeFileName(this.getFallbackChunkName())); + return this.name || (this.name = this.outputOptions.sanitizeFileName(this.getFallbackChunkName())); } getExportNames(): string[] { @@ -816,7 +815,7 @@ export default class Chunk { if (fileName) { this.fileName = fileName; } else { - this.name = sanitizeFileName(name || getChunkNameFromModule(facadedModule)); + this.name = this.outputOptions.sanitizeFileName(name || getChunkNameFromModule(facadedModule)); } } diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index b9dda715aa8..8c949b54c2d 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -649,6 +649,7 @@ export interface OutputOptions { preferConst?: boolean; preserveModules?: boolean; preserveModulesRoot?: string; + sanitizeFileName?: boolean | ((fileName: string) => string); sourcemap?: boolean | 'inline' | 'hidden'; sourcemapExcludeSources?: boolean; sourcemapFile?: string; @@ -693,6 +694,7 @@ export interface NormalizedOutputOptions { preferConst: boolean; preserveModules: boolean; preserveModulesRoot: string | undefined; + sanitizeFileName: (fileName: string) => string; sourcemap: boolean | 'inline' | 'hidden'; sourcemapExcludeSources: boolean; sourcemapFile: string | undefined; diff --git a/src/utils/FileEmitter.ts b/src/utils/FileEmitter.ts index 2c63cbd79f5..65520b91fc3 100644 --- a/src/utils/FileEmitter.ts +++ b/src/utils/FileEmitter.ts @@ -5,8 +5,8 @@ import { EmittedChunk, FilePlaceholder, NormalizedInputOptions, + NormalizedOutputOptions, OutputBundleWithPlaceholders, - PreRenderedAsset, WarningHandler } from '../rollup/types'; import { BuildPhase } from './buildPhase'; @@ -25,25 +25,21 @@ import { warnDeprecation } from './error'; import { extname } from './path'; -import { isPlainPathFragment } from './relativeId'; +import { isPathFragment } from './relativeId'; import { makeUnique, renderNamePattern } from './renderNamePattern'; -interface OutputSpecificFileData { - assetFileNames: string | ((assetInfo: PreRenderedAsset) => string); - bundle: OutputBundleWithPlaceholders; -} - function generateAssetFileName( name: string | undefined, source: string | Uint8Array, - output: OutputSpecificFileData + outputOptions: NormalizedOutputOptions, + bundle: OutputBundleWithPlaceholders ): string { - const emittedName = name || 'asset'; + const emittedName = outputOptions.sanitizeFileName(name || 'asset'); return makeUnique( renderNamePattern( - typeof output.assetFileNames === 'function' - ? output.assetFileNames({ name, source, type: 'asset' }) - : output.assetFileNames, + typeof outputOptions.assetFileNames === 'function' + ? outputOptions.assetFileNames({ name, source, type: 'asset' }) + : outputOptions.assetFileNames, 'output.assetFileNames', { hash() { @@ -58,7 +54,7 @@ function generateAssetFileName( name: () => emittedName.substr(0, emittedName.length - extname(emittedName).length) } ), - output.bundle + bundle ); } @@ -110,14 +106,9 @@ function hasValidType( ); } -function hasValidName(emittedFile: { - type: 'asset' | 'chunk'; - [key: string]: unknown; -}): emittedFile is EmittedFile { +function hasValidName(emittedFile: { type: 'asset' | 'chunk'; [key: string]: unknown; }): emittedFile is EmittedFile { const validatedName = emittedFile.fileName || emittedFile.name; - return ( - !validatedName || (typeof validatedName === 'string' && isPlainPathFragment(validatedName)) - ); + return !validatedName || typeof validatedName === 'string' && !isPathFragment(validatedName); } function getValidSource( @@ -155,9 +146,10 @@ function getChunkFileName( } export class FileEmitter { + private bundle: OutputBundleWithPlaceholders | null = null; private facadeChunkByModule: Map | null = null; private filesByReferenceId: Map; - private output: OutputSpecificFileData | null = null; + private outputOptions: NormalizedOutputOptions | null = null; constructor( private readonly graph: Graph, @@ -189,7 +181,7 @@ export class FileEmitter { if (!hasValidName(emittedFile)) { return error( errFailedValidation( - `The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths and do not contain invalid characters, received "${ + `The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths, received "${ emittedFile.fileName || emittedFile.name }".` ) @@ -226,8 +218,8 @@ export class FileEmitter { return error(errAssetSourceAlreadySet(consumedFile.name || referenceId)); } const source = getValidSource(requestedSource, consumedFile, referenceId); - if (this.output) { - this.finalizeAsset(consumedFile, source, referenceId, this.output); + if (this.bundle) { + this.finalizeAsset(consumedFile, source, referenceId, this.bundle); } else { consumedFile.source = source; } @@ -235,22 +227,20 @@ export class FileEmitter { public setOutputBundle = ( outputBundle: OutputBundleWithPlaceholders, - assetFileNames: string | ((assetInfo: PreRenderedAsset) => string), + outputOptions: NormalizedOutputOptions, facadeChunkByModule: Map ): void => { - this.output = { - assetFileNames, - bundle: outputBundle - }; + this.outputOptions = outputOptions; + this.bundle = outputBundle; this.facadeChunkByModule = facadeChunkByModule; for (const emittedFile of this.filesByReferenceId.values()) { if (emittedFile.fileName) { - reserveFileNameInBundle(emittedFile.fileName, this.output.bundle, this.options.onwarn); + reserveFileNameInBundle(emittedFile.fileName, this.bundle, this.options.onwarn); } } for (const [referenceId, consumedFile] of this.filesByReferenceId.entries()) { if (consumedFile.type === 'asset' && consumedFile.source !== undefined) { - this.finalizeAsset(consumedFile, consumedFile.source, referenceId, this.output); + this.finalizeAsset(consumedFile, consumedFile.source, referenceId, this.bundle); } } }; @@ -285,12 +275,12 @@ export class FileEmitter { consumedAsset, emittedAsset.fileName || emittedAsset.name || emittedAsset.type ); - if (this.output) { + if (this.bundle) { if (emittedAsset.fileName) { - reserveFileNameInBundle(emittedAsset.fileName, this.output.bundle, this.options.onwarn); + reserveFileNameInBundle(emittedAsset.fileName, this.bundle, this.options.onwarn); } if (source !== undefined) { - this.finalizeAsset(consumedAsset, source, referenceId, this.output); + this.finalizeAsset(consumedAsset, source, referenceId, this.bundle); } } return referenceId; @@ -328,18 +318,18 @@ export class FileEmitter { consumedFile: ConsumedFile, source: string | Uint8Array, referenceId: string, - output: OutputSpecificFileData + bundle: OutputBundleWithPlaceholders ): void { const fileName = consumedFile.fileName || - findExistingAssetFileNameWithSource(output.bundle, source) || - generateAssetFileName(consumedFile.name, source, output); + findExistingAssetFileNameWithSource(bundle, source) || + generateAssetFileName(consumedFile.name, source, this.outputOptions!, bundle); // We must not modify the original assets to avoid interaction between outputs const assetWithFileName = { ...consumedFile, source, fileName }; this.filesByReferenceId.set(referenceId, assetWithFileName); const options = this.options; - output.bundle[fileName] = { + bundle[fileName] = { fileName, name: consumedFile.name, get isAsset(): true { diff --git a/src/utils/PluginContext.ts b/src/utils/PluginContext.ts index 70ca86d58c3..87214faffdf 100644 --- a/src/utils/PluginContext.ts +++ b/src/utils/PluginContext.ts @@ -103,7 +103,7 @@ export function getPluginContext( true, options ), - emitFile: fileEmitter.emitFile, + emitFile: fileEmitter.emitFile.bind(fileEmitter), error(err): never { return throwPluginError(err, plugin.name); }, diff --git a/src/utils/PluginDriver.ts b/src/utils/PluginDriver.ts index 61dfce57095..0023aabaaf4 100644 --- a/src/utils/PluginDriver.ts +++ b/src/utils/PluginDriver.ts @@ -7,6 +7,7 @@ import { EmitFile, FirstPluginHooks, NormalizedInputOptions, + NormalizedOutputOptions, OutputBundleWithPlaceholders, OutputPluginHooks, ParallelPluginHooks, @@ -14,7 +15,6 @@ import { PluginContext, PluginHooks, PluginValueHooks, - PreRenderedAsset, SequentialPluginHooks, SerializablePluginCache, SyncPluginHooks @@ -74,7 +74,7 @@ export class PluginDriver { public getFileName: (fileReferenceId: string) => string; public setOutputBundle: ( outputBundle: OutputBundleWithPlaceholders, - assetFileNames: string | ((assetInfo: PreRenderedAsset) => string), + outputOptions: NormalizedOutputOptions, facadeChunkByModule: Map ) => void; @@ -92,15 +92,11 @@ export class PluginDriver { ) { warnDeprecatedHooks(userPlugins, options); this.pluginCache = pluginCache; - this.fileEmitter = new FileEmitter( - graph, - options, - basePluginDriver && basePluginDriver.fileEmitter - ); - this.emitFile = this.fileEmitter.emitFile; - this.getFileName = this.fileEmitter.getFileName; - this.finaliseAssets = this.fileEmitter.assertAssetsFinalized; - this.setOutputBundle = this.fileEmitter.setOutputBundle; + this.fileEmitter = new FileEmitter(graph, options, basePluginDriver && basePluginDriver.fileEmitter); + this.emitFile = this.fileEmitter.emitFile.bind(this.fileEmitter); + this.getFileName = this.fileEmitter.getFileName.bind(this.fileEmitter); + this.finaliseAssets = this.fileEmitter.assertAssetsFinalized.bind(this.fileEmitter); + this.setOutputBundle = this.fileEmitter.setOutputBundle.bind(this.fileEmitter); this.plugins = userPlugins.concat(basePluginDriver ? basePluginDriver.plugins : []); const existingPluginNames = new Set(); for (const plugin of this.plugins) { diff --git a/src/utils/options/mergeOptions.ts b/src/utils/options/mergeOptions.ts index 822e26c0f8f..0c0ebeec6be 100644 --- a/src/utils/options/mergeOptions.ts +++ b/src/utils/options/mergeOptions.ts @@ -223,6 +223,7 @@ function mergeOutputOptions( preferConst: getOption('preferConst'), preserveModules: getOption('preserveModules'), preserveModulesRoot: getOption('preserveModulesRoot'), + sanitizeFileName: getOption('sanitizeFileName'), sourcemap: getOption('sourcemap'), sourcemapExcludeSources: getOption('sourcemapExcludeSources'), sourcemapFile: getOption('sourcemapFile'), diff --git a/src/utils/options/normalizeOutputOptions.ts b/src/utils/options/normalizeOutputOptions.ts index 5cf14024e29..1531b6ea8d4 100644 --- a/src/utils/options/normalizeOutputOptions.ts +++ b/src/utils/options/normalizeOutputOptions.ts @@ -14,6 +14,7 @@ import { import { ensureArray } from '../ensureArray'; import { errInvalidExportOptionValue, error, warnDeprecation } from '../error'; import { resolve } from '../path'; +import { sanitizeFileName as defaultSanitizeFileName } from '../sanitizeFileName'; import { GenericConfigObject, warnUnknownOptions } from './options'; export function normalizeOutputOptions( @@ -66,6 +67,7 @@ export function normalizeOutputOptions( preferConst: (config.preferConst as boolean | undefined) || false, preserveModules, preserveModulesRoot: getPreserveModulesRoot(config), + sanitizeFileName: (typeof config.sanitizeFileName === 'function' ? config.sanitizeFileName : config.sanitizeFileName === false ? (id) => id : defaultSanitizeFileName) as NormalizedOutputOptions['sanitizeFileName'], sourcemap: (config.sourcemap as boolean | 'inline' | 'hidden' | undefined) || false, sourcemapExcludeSources: (config.sourcemapExcludeSources as boolean | undefined) || false, sourcemapFile: config.sourcemapFile as string | undefined, diff --git a/src/utils/relativeId.ts b/src/utils/relativeId.ts index e3d3d36475b..6618a683459 100644 --- a/src/utils/relativeId.ts +++ b/src/utils/relativeId.ts @@ -1,5 +1,4 @@ import { basename, extname, isAbsolute, relative, resolve } from './path'; -import { sanitizeFileName } from './sanitizeFileName'; export function getAliasName(id: string) { const base = basename(id); @@ -11,12 +10,7 @@ export default function relativeId(id: string) { return relative(resolve(), id); } -export function isPlainPathFragment(name: string) { - // not starting with "/", "./", "../" - return ( - name[0] !== '/' && - !(name[0] === '.' && (name[1] === '/' || name[1] === '.')) && - sanitizeFileName(name) === name && - !isAbsolute(name) - ); +export function isPathFragment(name: string) { + // starting with "/", "./", "../", "C:/" + return name[0] === '/' || name[0] === '.' && (name[1] === '/' || name[1] === '.') || isAbsolute(name); } diff --git a/src/utils/renderNamePattern.ts b/src/utils/renderNamePattern.ts index 13690decc71..debc84c4ca8 100644 --- a/src/utils/renderNamePattern.ts +++ b/src/utils/renderNamePattern.ts @@ -1,16 +1,12 @@ import { errFailedValidation, error } from './error'; import { extname } from './path'; -import { isPlainPathFragment } from './relativeId'; +import { isPathFragment } from './relativeId'; -export function renderNamePattern( - pattern: string, - patternName: string, - replacements: { [name: string]: () => string } -) { - if (!isPlainPathFragment(pattern)) +export function renderNamePattern(pattern: string, patternName: string, replacements: { [name: string]: () => string }) { + if (isPathFragment(pattern)) return error( errFailedValidation( - `Invalid pattern "${pattern}" for "${patternName}", patterns can be neither absolute nor relative paths and must not contain invalid characters.` + `Invalid pattern "${pattern}" for "${patternName}", patterns can be neither absolute nor relative paths.` ) ); return pattern.replace(/\[(\w+)\]/g, (_match, type) => { @@ -20,7 +16,7 @@ export function renderNamePattern( ); } const replacement = replacements[type](); - if (!isPlainPathFragment(replacement)) + if (isPathFragment(replacement)) return error( errFailedValidation( `Invalid substitution "${replacement}" for placeholder "[${type}]" in "${patternName}" pattern, can be neither absolute nor relative path.` diff --git a/test/chunking-form/samples/custom-sanitizer/_config.js b/test/chunking-form/samples/custom-sanitizer/_config.js new file mode 100644 index 00000000000..0abca0b2f08 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_config.js @@ -0,0 +1,18 @@ +module.exports = { + description: 'supports custom file name sanitizer functions', + options: { + plugins: [ + { + buildStart() { + this.emitFile({ type: 'asset', name: 'asset.txt', source: 'asset' }); + } + } + ], + output: { + assetFileNames: '[name][extname]', + sanitizeFileName(id) { + return id.replace(/a/g, '_'); + } + } + } +}; diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/amd/_sset.txt b/test/chunking-form/samples/custom-sanitizer/_expected/amd/_sset.txt new file mode 100644 index 00000000000..a95e94f6604 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/amd/_sset.txt @@ -0,0 +1 @@ +asset \ No newline at end of file diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/amd/m_in.js b/test/chunking-form/samples/custom-sanitizer/_expected/amd/m_in.js new file mode 100644 index 00000000000..6ec9254acf0 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/amd/m_in.js @@ -0,0 +1,5 @@ +define(function () { 'use strict'; + + console.log('ok'); + +}); diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/cjs/_sset.txt b/test/chunking-form/samples/custom-sanitizer/_expected/cjs/_sset.txt new file mode 100644 index 00000000000..a95e94f6604 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/cjs/_sset.txt @@ -0,0 +1 @@ +asset \ No newline at end of file diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/cjs/m_in.js b/test/chunking-form/samples/custom-sanitizer/_expected/cjs/m_in.js new file mode 100644 index 00000000000..faea9b6d894 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/cjs/m_in.js @@ -0,0 +1,3 @@ +'use strict'; + +console.log('ok'); diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/es/_sset.txt b/test/chunking-form/samples/custom-sanitizer/_expected/es/_sset.txt new file mode 100644 index 00000000000..a95e94f6604 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/es/_sset.txt @@ -0,0 +1 @@ +asset \ No newline at end of file diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/es/m_in.js b/test/chunking-form/samples/custom-sanitizer/_expected/es/m_in.js new file mode 100644 index 00000000000..37108886b56 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/es/m_in.js @@ -0,0 +1 @@ +console.log('ok'); diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/system/_sset.txt b/test/chunking-form/samples/custom-sanitizer/_expected/system/_sset.txt new file mode 100644 index 00000000000..a95e94f6604 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/system/_sset.txt @@ -0,0 +1 @@ +asset \ No newline at end of file diff --git a/test/chunking-form/samples/custom-sanitizer/_expected/system/m_in.js b/test/chunking-form/samples/custom-sanitizer/_expected/system/m_in.js new file mode 100644 index 00000000000..2181247e7d3 --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/_expected/system/m_in.js @@ -0,0 +1,10 @@ +System.register([], function () { + 'use strict'; + return { + execute: function () { + + console.log('ok'); + + } + }; +}); diff --git a/test/chunking-form/samples/custom-sanitizer/main.js b/test/chunking-form/samples/custom-sanitizer/main.js new file mode 100644 index 00000000000..bb06158e22d --- /dev/null +++ b/test/chunking-form/samples/custom-sanitizer/main.js @@ -0,0 +1,2 @@ +console.log('ok'); + diff --git a/test/function/samples/deprecated/emit-asset/invalid-asset-name/_config.js b/test/function/samples/deprecated/emit-asset/invalid-asset-name/_config.js index 8add304a305..f807478d8f4 100644 --- a/test/function/samples/deprecated/emit-asset/invalid-asset-name/_config.js +++ b/test/function/samples/deprecated/emit-asset/invalid-asset-name/_config.js @@ -13,7 +13,7 @@ module.exports = { code: 'PLUGIN_ERROR', hook: 'buildStart', message: - 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths and do not contain invalid characters, received "/test.ext".', + 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths, received "/test.ext".', plugin: 'test-plugin', pluginCode: 'VALIDATION_ERROR' } diff --git a/test/function/samples/emit-file/invalid-asset-name/_config.js b/test/function/samples/emit-file/invalid-asset-name/_config.js index 58ff955cc15..1d58fe9c100 100644 --- a/test/function/samples/emit-file/invalid-asset-name/_config.js +++ b/test/function/samples/emit-file/invalid-asset-name/_config.js @@ -12,7 +12,7 @@ module.exports = { code: 'PLUGIN_ERROR', hook: 'buildStart', message: - 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths and do not contain invalid characters, received "/test.ext".', + 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths, received "/test.ext".', plugin: 'test-plugin', pluginCode: 'VALIDATION_ERROR' } diff --git a/test/function/samples/emit-file/invalid-asset-name2/_config.js b/test/function/samples/emit-file/invalid-asset-name2/_config.js deleted file mode 100644 index c0ff923d737..00000000000 --- a/test/function/samples/emit-file/invalid-asset-name2/_config.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - description: 'throws for invalid asset names with special characters', - options: { - plugins: { - name: 'test-plugin', - buildStart() { - this.emitFile({ type: 'asset', name: '\0test.ext', source: 'content' }); - } - } - }, - error: { - code: 'PLUGIN_ERROR', - hook: 'buildStart', - message: - 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths and do not contain invalid characters, received "\u0000test.ext".', - plugin: 'test-plugin', - pluginCode: 'VALIDATION_ERROR' - } -}; diff --git a/test/function/samples/emit-file/invalid-asset-name2/main.js b/test/function/samples/emit-file/invalid-asset-name2/main.js deleted file mode 100644 index c4b940fc952..00000000000 --- a/test/function/samples/emit-file/invalid-asset-name2/main.js +++ /dev/null @@ -1 +0,0 @@ -throw new Error('should not build'); diff --git a/test/function/samples/emit-file/invalid-asset-name3/_config.js b/test/function/samples/emit-file/invalid-asset-name3/_config.js index da96f00fc22..5d720fda2e8 100644 --- a/test/function/samples/emit-file/invalid-asset-name3/_config.js +++ b/test/function/samples/emit-file/invalid-asset-name3/_config.js @@ -12,7 +12,7 @@ module.exports = { code: 'PLUGIN_ERROR', hook: 'buildStart', message: - 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths and do not contain invalid characters, received "F:\\test.ext".', + 'The "fileName" or "name" properties of emitted files must be strings that are neither absolute nor relative paths, received "F:\\test.ext".', plugin: 'test-plugin', pluginCode: 'VALIDATION_ERROR' } diff --git a/test/function/samples/emit-file/non-invalid-asset-name/_config.js b/test/function/samples/emit-file/non-invalid-asset-name/_config.js new file mode 100644 index 00000000000..e5474d5df05 --- /dev/null +++ b/test/function/samples/emit-file/non-invalid-asset-name/_config.js @@ -0,0 +1,11 @@ +module.exports = { + description: 'throws for invalid asset names with special characters', + options: { + plugins: { + name: 'test-plugin', + buildStart() { + this.emitFile({ type: 'asset', name: '\0test.ext', source: 'content' }); + } + } + } +}; diff --git a/test/function/samples/emit-file/non-invalid-asset-name/main.js b/test/function/samples/emit-file/non-invalid-asset-name/main.js new file mode 100644 index 00000000000..bfeff5f065d --- /dev/null +++ b/test/function/samples/emit-file/non-invalid-asset-name/main.js @@ -0,0 +1,2 @@ +console.log('should build'); + diff --git a/test/function/samples/invalid-pattern/_config.js b/test/function/samples/invalid-pattern/_config.js index 6275e9e38ac..3c2a6b7aa58 100644 --- a/test/function/samples/invalid-pattern/_config.js +++ b/test/function/samples/invalid-pattern/_config.js @@ -1,11 +1,11 @@ module.exports = { description: 'throws for invalid patterns', options: { - output: { entryFileNames: '\0main.js' } + output: { entryFileNames: '../main.js' } }, generateError: { code: 'VALIDATION_ERROR', message: - 'Invalid pattern "\0main.js" for "output.entryFileNames", patterns can be neither absolute nor relative paths and must not contain invalid characters.' + 'Invalid pattern "../main.js" for "output.entryFileNames", patterns can be neither absolute nor relative paths.' } }; diff --git a/test/function/samples/invalid-pattern/main.js b/test/function/samples/invalid-pattern/main.js index a9244a453fb..e69de29bb2d 100644 --- a/test/function/samples/invalid-pattern/main.js +++ b/test/function/samples/invalid-pattern/main.js @@ -1 +0,0 @@ -throw new Error('Not executed'); diff --git a/test/function/samples/pattern-encodings/_config.js b/test/function/samples/pattern-encodings/_config.js new file mode 100644 index 00000000000..9aefc4fd6c0 --- /dev/null +++ b/test/function/samples/pattern-encodings/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'throws for invalid patterns', + options: { + output: { entryFileNames: '\0main.js' } + } +}; diff --git a/test/function/samples/pattern-encodings/main.js b/test/function/samples/pattern-encodings/main.js new file mode 100644 index 00000000000..bb06158e22d --- /dev/null +++ b/test/function/samples/pattern-encodings/main.js @@ -0,0 +1,2 @@ +console.log('ok'); + diff --git a/test/hooks/index.js b/test/hooks/index.js index 30e26bd75a8..dbca152d9c9 100644 --- a/test/hooks/index.js +++ b/test/hooks/index.js @@ -1100,6 +1100,27 @@ describe('hooks', () => { }); }); + it('supports disabling sanitization for in-memory / in-browser / non-fs builds', () => { + return rollup + .rollup({ + input: 'input.js', + plugins: [{ + resolveId: id => id, + load: () => `export default 5` + }] + }) + .then(bundle => { + return bundle.generate({ + format: 'es', + sanitizeFileName: false, + entryFileNames: 'test:[name]' + }); + }) + .then(({ output }) => { + assert.strictEqual(output[0].fileName, 'test:input'); + }); + }); + describe('deprecated', () => { it('caches chunk emission in transform hook', () => { let cache; diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 72c376d9724..b0cadbe0fc9 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,6 +1,6 @@ exports.input = 'acorn, acornInjectPlugins, cache, context, experimentalCacheExpiry, external, inlineDynamicImports, input, makeAbsoluteExternalsRelative, manualChunks, moduleContext, onwarn, perf, plugins, preserveEntrySignatures, preserveModules, preserveSymlinks, shimMissingExports, strictDeprecations, treeshake, watch'; exports.flags = - 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, exports, extend, external, externalLiveBindings, f, failAfterWarnings, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, makeAbsoluteExternalsRelative, manualChunks, minifyInternalExports, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, w, waitForBundleInput, watch'; + 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, compact, config, context, d, dir, dynamicImportFunction, e, entryFileNames, environment, esModule, experimentalCacheExpiry, exports, extend, external, externalLiveBindings, f, failAfterWarnings, file, footer, format, freeze, g, globals, h, hoistTransitiveImports, i, indent, inlineDynamicImports, input, interop, intro, m, makeAbsoluteExternalsRelative, manualChunks, minifyInternalExports, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, p, paths, perf, plugin, plugins, preferConst, preserveEntrySignatures, preserveModules, preserveModulesRoot, preserveSymlinks, sanitizeFileName, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, stdin, strict, strictDeprecations, systemNullSetters, treeshake, v, validate, w, waitForBundleInput, watch'; exports.output = - 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, globals, hoistTransitiveImports, indent, inlineDynamicImports, interop, intro, manualChunks, minifyInternalExports, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, preserveModules, preserveModulesRoot, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict, systemNullSetters, validate'; + 'amd, assetFileNames, banner, chunkFileNames, compact, dir, dynamicImportFunction, entryFileNames, esModule, exports, extend, externalLiveBindings, file, footer, format, freeze, globals, hoistTransitiveImports, indent, inlineDynamicImports, interop, intro, manualChunks, minifyInternalExports, name, namespaceToStringTag, noConflict, outro, paths, plugins, preferConst, preserveModules, preserveModulesRoot, sanitizeFileName, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict, systemNullSetters, validate';