From 7218ab749676c49e68ad44b12efbf63f0ab5f4f0 Mon Sep 17 00:00:00 2001 From: ZHAO Jin-Xiang Date: Thu, 30 Nov 2023 14:17:51 +0800 Subject: [PATCH 1/4] chore: improve typing for scripts (#9709) --- rollup.config.js | 50 +++++++++++++++++++------- rollup.dts.config.js | 38 ++++++++++++-------- scripts/aliases.js | 3 +- scripts/build.js | 5 ++- scripts/release.js | 83 +++++++++++++++++++++++++++++++++----------- scripts/utils.js | 8 ++++- tsconfig.build.json | 4 +-- tsconfig.json | 3 +- 8 files changed, 138 insertions(+), 56 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 421fa9c37..b7d38e452 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,5 @@ // @ts-check +import assert from 'node:assert/strict' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import path from 'node:path' @@ -14,6 +15,14 @@ import alias from '@rollup/plugin-alias' import { entries } from './scripts/aliases.js' import { inlineEnums } from './scripts/inline-enums.js' +/** + * @template T + * @template {keyof T} K + * @typedef { Omit & Required> } MarkRequired + */ +/** @typedef {'cjs' | 'esm-bundler' | 'global' | 'global-runtime' | 'esm-browser' | 'esm-bundler-runtime' | 'esm-browser-runtime'} PackageFormat */ +/** @typedef {MarkRequired} OutputOptions */ + if (!process.env.TARGET) { throw new Error('TARGET package must be specified via --environment flag.') } @@ -27,34 +36,35 @@ const consolidatePkg = require('@vue/consolidate/package.json') const packagesDir = path.resolve(__dirname, 'packages') const packageDir = path.resolve(packagesDir, process.env.TARGET) -const resolve = p => path.resolve(packageDir, p) +const resolve = (/** @type {string} */ p) => path.resolve(packageDir, p) const pkg = require(resolve(`package.json`)) const packageOptions = pkg.buildOptions || {} const name = packageOptions.filename || path.basename(packageDir) const [enumPlugin, enumDefines] = inlineEnums() +/** @type {Record} */ const outputConfigs = { 'esm-bundler': { file: resolve(`dist/${name}.esm-bundler.js`), - format: `es` + format: 'es' }, 'esm-browser': { file: resolve(`dist/${name}.esm-browser.js`), - format: `es` + format: 'es' }, cjs: { file: resolve(`dist/${name}.cjs.js`), - format: `cjs` + format: 'cjs' }, global: { file: resolve(`dist/${name}.global.js`), - format: `iife` + format: 'iife' }, // runtime-only builds, for main "vue" package only 'esm-bundler-runtime': { file: resolve(`dist/${name}.runtime.esm-bundler.js`), - format: `es` + format: 'es' }, 'esm-browser-runtime': { file: resolve(`dist/${name}.runtime.esm-browser.js`), @@ -66,8 +76,13 @@ const outputConfigs = { } } +/** @type {ReadonlyArray} */ const defaultFormats = ['esm-bundler', 'cjs'] -const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',') +/** @type {ReadonlyArray} */ +const inlineFormats = /** @type {any} */ ( + process.env.FORMATS && process.env.FORMATS.split(',') +) +/** @type {ReadonlyArray} */ const packageFormats = inlineFormats || packageOptions.formats || defaultFormats const packageConfigs = process.env.PROD_ONLY ? [] @@ -89,6 +104,13 @@ if (process.env.NODE_ENV === 'production') { export default packageConfigs +/** + * + * @param {PackageFormat} format + * @param {OutputOptions} output + * @param {ReadonlyArray} plugins + * @returns {import('rollup').RollupOptions} + */ function createConfig(format, output, plugins = []) { if (!output) { console.log(pico.yellow(`invalid format: "${format}"`)) @@ -132,6 +154,7 @@ function createConfig(format, output, plugins = []) { } function resolveDefine() { + /** @type {Record} */ const replacements = { __COMMIT__: `"${process.env.COMMIT}"`, __VERSION__: `"${masterVersion}"`, @@ -162,7 +185,6 @@ function createConfig(format, output, plugins = []) { if (!isBundlerESMBuild) { // hard coded dev/prod builds - // @ts-ignore replacements.__DEV__ = String(!isProductionBuild) } @@ -170,7 +192,9 @@ function createConfig(format, output, plugins = []) { //__RUNTIME_COMPILE__=true pnpm build runtime-core Object.keys(replacements).forEach(key => { if (key in process.env) { - replacements[key] = process.env[key] + const value = process.env[key] + assert(typeof value === 'string') + replacements[key] = value } }) return replacements @@ -207,7 +231,6 @@ function createConfig(format, output, plugins = []) { } if (Object.keys(replacements).length) { - // @ts-ignore return [replace({ values: replacements, preventAssignment: true })] } else { return [] @@ -245,6 +268,7 @@ function createConfig(format, output, plugins = []) { function resolveNodePlugins() { // we are bundling forked consolidate.js in compiler-sfc which dynamically // requires a ton of template engines which should be ignored. + /** @type {ReadonlyArray} */ let cjsIgnores = [] if ( pkg.name === '@vue/compiler-sfc' || @@ -304,7 +328,7 @@ function createConfig(format, output, plugins = []) { ], output, onwarn: (msg, warn) => { - if (!/Circular/.test(msg)) { + if (msg.code !== 'CIRCULAR_DEPENDENCY') { warn(msg) } }, @@ -314,14 +338,14 @@ function createConfig(format, output, plugins = []) { } } -function createProductionConfig(format) { +function createProductionConfig(/** @type {PackageFormat} */ format) { return createConfig(format, { file: resolve(`dist/${name}.${format}.prod.js`), format: outputConfigs[format].format }) } -function createMinifiedConfig(format) { +function createMinifiedConfig(/** @type {PackageFormat} */ format) { return createConfig( format, { diff --git a/rollup.dts.config.js b/rollup.dts.config.js index 3e45167bc..a9b3a0cda 100644 --- a/rollup.dts.config.js +++ b/rollup.dts.config.js @@ -1,6 +1,7 @@ // @ts-check +import assert from 'node:assert/strict' import { parse } from '@babel/parser' -import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs' +import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs' import MagicString from 'magic-string' import dts from 'rollup-plugin-dts' @@ -70,15 +71,16 @@ function patchTypes(pkg) { if (!node.id) { return } - // @ts-ignore + assert(node.id.type === 'Identifier') const name = node.id.name if (name.startsWith('_')) { return } shouldRemoveExport.add(name) if (isExported.has(name)) { - // @ts-ignore - s.prependLeft((parentDecl || node).start, `export `) + const start = (parentDecl || node).start + assert(typeof start === 'number') + s.prependLeft(start, `export `) } } @@ -102,9 +104,10 @@ function patchTypes(pkg) { if (node.type === 'VariableDeclaration') { processDeclaration(node.declarations[0], node) if (node.declarations.length > 1) { + assert(typeof node.start === 'number') + assert(typeof node.end === 'number') throw new Error( `unhandled declare const with more than one declarators:\n${code.slice( - // @ts-ignore node.start, node.end )}` @@ -131,7 +134,7 @@ function patchTypes(pkg) { spec.type === 'ExportSpecifier' && shouldRemoveExport.has(spec.local.name) ) { - // @ts-ignore + assert(spec.exported.type === 'Identifier') const exported = spec.exported.name if (exported !== spec.local.name) { // this only happens if we have something like @@ -141,19 +144,27 @@ function patchTypes(pkg) { } const next = node.specifiers[i + 1] if (next) { - // @ts-ignore + assert(typeof spec.start === 'number') + assert(typeof next.start === 'number') s.remove(spec.start, next.start) } else { // last one const prev = node.specifiers[i - 1] - // @ts-ignore - s.remove(prev ? prev.end : spec.start, spec.end) + assert(typeof spec.start === 'number') + assert(typeof spec.end === 'number') + s.remove( + prev + ? (assert(typeof prev.end === 'number'), prev.end) + : spec.start, + spec.end + ) } removed++ } } if (removed === node.specifiers.length) { - // @ts-ignore + assert(typeof node.start === 'number') + assert(typeof node.end === 'number') s.remove(node.start, node.end) } } @@ -186,11 +197,8 @@ function copyMts() { return { name: 'copy-vue-mts', writeBundle(_, bundle) { - writeFileSync( - 'packages/vue/dist/vue.d.mts', - // @ts-ignore - bundle['vue.d.ts'].code - ) + assert('code' in bundle['vue.d.ts']) + writeFileSync('packages/vue/dist/vue.d.mts', bundle['vue.d.ts'].code) } } } diff --git a/scripts/aliases.js b/scripts/aliases.js index 34a7c6435..907196745 100644 --- a/scripts/aliases.js +++ b/scripts/aliases.js @@ -4,7 +4,7 @@ import { readdirSync, statSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' -const resolveEntryForPkg = p => +const resolveEntryForPkg = (/** @type {string} */ p) => path.resolve( fileURLToPath(import.meta.url), `../../packages/${p}/src/index.ts` @@ -12,6 +12,7 @@ const resolveEntryForPkg = p => const dirs = readdirSync(new URL('../packages', import.meta.url)) +/** @type {Record} */ const entries = { vue: resolveEntryForPkg('vue'), 'vue/compiler-sfc': resolveEntryForPkg('compiler-sfc'), diff --git a/scripts/build.js b/scripts/build.js index b96e576ca..aa56a1327 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -38,6 +38,7 @@ const prodOnly = !devOnly && (args.prodOnly || args.p) const buildTypes = args.withTypes || args.t const sourceMap = args.sourcemap || args.s const isRelease = args.release +/** @type {boolean | undefined} */ const buildAllMatching = args.all || args.a const writeSize = args.size const commit = execaSync('git', ['rev-parse', '--short=7', 'HEAD']).stdout @@ -102,7 +103,9 @@ async function runParallel(maxConcurrency, source, iteratorFn) { ret.push(p) if (maxConcurrency <= source.length) { - const e = p.then(() => executing.splice(executing.indexOf(e), 1)) + const e = p.then(() => { + executing.splice(executing.indexOf(e), 1) + }) executing.push(e) if (executing.length >= maxConcurrency) { await Promise.race(executing) diff --git a/scripts/release.js b/scripts/release.js index a9484ff5f..fddf4ca67 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -9,6 +9,15 @@ import { execa } from 'execa' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' +/** + * @typedef {{ + * name: string + * version: string + * dependencies?: { [dependenciesPackageName: string]: string } + * peerDependencies?: { [peerDependenciesPackageName: string]: string } + * }} Package + */ + let versionUpdated = false const { prompt } = enquirer @@ -25,6 +34,7 @@ const args = minimist(process.argv.slice(2), { const preId = args.preid || semver.prerelease(currentVersion)?.[0] const isDryRun = args.dry +/** @type {boolean | undefined} */ let skipTests = args.skipTests const skipBuild = args.skipBuild const isCanary = args.canary @@ -43,7 +53,7 @@ const packages = fs } }) -const isCorePackage = pkgName => { +const isCorePackage = (/** @type {string} */ pkgName) => { if (!pkgName) return if (pkgName === 'vue' || pkgName === '@vue/compat') { @@ -56,7 +66,7 @@ const isCorePackage = pkgName => { ) } -const renamePackageToCanary = pkgName => { +const renamePackageToCanary = (/** @type {string} */ pkgName) => { if (pkgName === 'vue') { return '@vue/canary' } @@ -68,25 +78,37 @@ const renamePackageToCanary = pkgName => { return pkgName } -const keepThePackageName = pkgName => pkgName +const keepThePackageName = (/** @type {string} */ pkgName) => pkgName +/** @type {string[]} */ const skippedPackages = [] +/** @type {ReadonlyArray} */ const versionIncrements = [ 'patch', 'minor', 'major', - ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : []) + ...(preId + ? /** @type {const} */ (['prepatch', 'preminor', 'premajor', 'prerelease']) + : []) ] -const inc = i => semver.inc(currentVersion, i, preId) -const run = (bin, args, opts = {}) => - execa(bin, args, { stdio: 'inherit', ...opts }) -const dryRun = (bin, args, opts = {}) => - console.log(pico.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts) +const inc = (/** @type {import('semver').ReleaseType} */ i) => + semver.inc(currentVersion, i, preId) +const run = async ( + /** @type {string} */ bin, + /** @type {ReadonlyArray} */ args, + /** @type {import('execa').Options} */ opts = {} +) => execa(bin, args, { stdio: 'inherit', ...opts }) +const dryRun = async ( + /** @type {string} */ bin, + /** @type {ReadonlyArray} */ args, + /** @type {import('execa').Options} */ opts = {} +) => console.log(pico.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts) const runIfNotDry = isDryRun ? dryRun : run -const getPkgRoot = pkg => path.resolve(__dirname, '../packages/' + pkg) -const step = msg => console.log(pico.cyan(msg)) +const getPkgRoot = (/** @type {string} */ pkg) => + path.resolve(__dirname, '../packages/' + pkg) +const step = (/** @type {string} */ msg) => console.log(pico.cyan(msg)) async function main() { if (!(await isInSyncWithRemote())) { @@ -137,7 +159,7 @@ async function main() { semver.inc(latestSameDayPatch, 'prerelease', args.tag) ) } - } catch (e) { + } catch (/** @type {any} */ e) { if (/E404/.test(e.message)) { // the first patch version on that day } else { @@ -150,7 +172,7 @@ async function main() { if (!targetVersion) { // no explicit version, offer suggestions - // @ts-ignore + /** @type {{ release: string }} */ const { release } = await prompt({ type: 'select', name: 'release', @@ -159,16 +181,16 @@ async function main() { }) if (release === 'custom') { + /** @type {{ version: string }} */ const result = await prompt({ type: 'input', name: 'version', message: 'Input custom version', initial: currentVersion }) - // @ts-ignore targetVersion = result.version } else { - targetVersion = release.match(/\((.*)\)/)[1] + targetVersion = release.match(/\((.*)\)/)?.[1] ?? '' } } @@ -183,7 +205,7 @@ async function main() { : `Releasing v${targetVersion}...` ) } else { - // @ts-ignore + /** @type {{ yes: boolean }} */ const { yes: confirmRelease } = await prompt({ type: 'confirm', name: 'yes', @@ -201,7 +223,7 @@ async function main() { skipTests ||= isCIPassed if (isCIPassed && !skipPrompts) { - // @ts-ignore + /** @type {{ yes: boolean }} */ const { yes: promptSkipTests } = await prompt({ type: 'confirm', name: 'yes', @@ -246,7 +268,7 @@ async function main() { await run(`pnpm`, ['run', 'changelog']) if (!skipPrompts) { - // @ts-ignore + /** @type {{ yes: boolean }} */ const { yes: changelogOk } = await prompt({ type: 'confirm', name: 'yes', @@ -346,7 +368,7 @@ async function isInSyncWithRemote() { if (data.sha === (await getSha())) { return true } else { - // @ts-ignore + /** @type {{ yes: boolean }} */ const { yes } = await prompt({ type: 'confirm', name: 'yes', @@ -372,6 +394,10 @@ async function getBranch() { return (await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).stdout } +/** + * @param {string} version + * @param {(pkgName: string) => string} getNewPackageName + */ function updateVersions(version, getNewPackageName = keepThePackageName) { // 1. update root package.json updatePackage(path.resolve(__dirname, '..'), version, getNewPackageName) @@ -381,8 +407,14 @@ function updateVersions(version, getNewPackageName = keepThePackageName) { ) } +/** + * @param {string} pkgRoot + * @param {string} version + * @param {(pkgName: string) => string} getNewPackageName + */ function updatePackage(pkgRoot, version, getNewPackageName) { const pkgPath = path.resolve(pkgRoot, 'package.json') + /** @type {Package} */ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) pkg.name = getNewPackageName(pkg.name) pkg.version = version @@ -393,6 +425,12 @@ function updatePackage(pkgRoot, version, getNewPackageName) { fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n') } +/** + * @param {Package} pkg + * @param {'dependencies' | 'peerDependencies'} depType + * @param {string} version + * @param {(pkgName: string) => string} getNewPackageName + */ function updateDeps(pkg, depType, version, getNewPackageName) { const deps = pkg[depType] if (!deps) return @@ -408,6 +446,11 @@ function updateDeps(pkg, depType, version, getNewPackageName) { }) } +/** + * @param {string} pkgName + * @param {string} version + * @param {ReadonlyArray} additionalFlags + */ async function publishPackage(pkgName, version, additionalFlags) { if (skippedPackages.includes(pkgName)) { return @@ -443,7 +486,7 @@ async function publishPackage(pkgName, version, additionalFlags) { } ) console.log(pico.green(`Successfully published ${pkgName}@${version}`)) - } catch (e) { + } catch (/** @type {any} */ e) { if (e.stderr.match(/previously published/)) { console.log(pico.red(`Skipping already published: ${pkgName}`)) } else { diff --git a/scripts/utils.js b/scripts/utils.js index 9429074d7..789e1bcf6 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -16,7 +16,13 @@ export const targets = fs.readdirSync('packages').filter(f => { return true }) +/** + * + * @param {ReadonlyArray} partialTargets + * @param {boolean | undefined} includeAllMatching + */ export function fuzzyMatchTarget(partialTargets, includeAllMatching) { + /** @type {Array} */ const matched = [] partialTargets.forEach(partialTarget => { for (const target of targets) { @@ -34,7 +40,7 @@ export function fuzzyMatchTarget(partialTargets, includeAllMatching) { console.log() console.error( ` ${pico.white(pico.bgRed(' ERROR '))} ${pico.red( - `Target ${pico.underline(partialTargets)} not found!` + `Target ${pico.underline(partialTargets.toString())} not found!` )}` ) console.log() diff --git a/tsconfig.build.json b/tsconfig.build.json index 28036b1be..954103c0f 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -10,8 +10,6 @@ "packages/runtime-test", "packages/template-explorer", "packages/sfc-playground", - "packages/dts-test", - "rollup.config.js", - "scripts/*" + "packages/dts-test" ] } diff --git a/tsconfig.json b/tsconfig.json index 0aad03ad6..f47b7fc8e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,5 @@ "packages/vue/jsx-runtime", "scripts/*", "rollup.*.js" - ], - "exclude": ["rollup.config.js", "scripts/*"] + ] } From 2a507e32f0e2ef73813705a568b8633f68bda7a9 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 30 Nov 2023 16:37:17 +0800 Subject: [PATCH 2/4] feat(compiler-sfc): bump postcss-modules to v6 --- packages/compiler-sfc/package.json | 2 +- pnpm-lock.yaml | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index d5d71a06c..97564de2f 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -49,7 +49,7 @@ "lru-cache": "^10.1.0", "merge-source-map": "^1.1.0", "minimatch": "^9.0.3", - "postcss-modules": "^4.3.1", + "postcss-modules": "^6.0.0", "postcss-selector-parser": "^6.0.13", "pug": "^3.0.2", "sass": "^1.69.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2435b0350..6d5ef1d4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,8 +245,8 @@ importers: specifier: ^9.0.3 version: 9.0.3 postcss-modules: - specifier: ^4.3.1 - version: 4.3.1(postcss@8.4.31) + specifier: ^6.0.0 + version: 6.0.0(postcss@8.4.31) postcss-selector-parser: specifier: ^6.0.13 version: 6.0.13 @@ -3520,10 +3520,6 @@ packages: safer-buffer: 2.1.2 dev: true - /icss-replace-symbols@1.1.0: - resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} - dev: true - /icss-utils@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} @@ -4711,13 +4707,13 @@ packages: postcss: 8.4.31 dev: true - /postcss-modules@4.3.1(postcss@8.4.31): - resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} + /postcss-modules@6.0.0(postcss@8.4.31): + resolution: {integrity: sha512-7DGfnlyi/ju82BRzTIjWS5C4Tafmzl3R79YP/PASiocj+aa6yYphHhhKUOEoXQToId5rgyFgJ88+ccOUydjBXQ==} peerDependencies: postcss: ^8.0.0 dependencies: generic-names: 4.0.0 - icss-replace-symbols: 1.1.0 + icss-utils: 5.1.0(postcss@8.4.31) lodash.camelcase: 4.3.0 postcss: 8.4.31 postcss-modules-extract-imports: 3.0.0(postcss@8.4.31) From bcca475dbc58d76434cd8120b94929758cee2825 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Thu, 30 Nov 2023 09:39:40 +0100 Subject: [PATCH 3/4] feat(compiler-core): support accessing Error as global in template expressions (#7018) --- .../__tests__/transforms/transformExpressions.spec.ts | 8 ++++++++ packages/shared/src/globalsAllowList.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 0d18c1beb..a9697930c 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -161,6 +161,14 @@ describe('compiler: expression transform', () => { type: NodeTypes.COMPOUND_EXPRESSION, children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`] }) + + expect( + (parseWithExpressionTransform(`{{ new Error() }}`) as InterpolationNode) + .content + ).toMatchObject({ + type: NodeTypes.COMPOUND_EXPRESSION, + children: ['new ', { content: 'Error' }, '()'] + }) }) test('should not prefix reserved literals', () => { diff --git a/packages/shared/src/globalsAllowList.ts b/packages/shared/src/globalsAllowList.ts index 4af518c22..210650e3e 100644 --- a/packages/shared/src/globalsAllowList.ts +++ b/packages/shared/src/globalsAllowList.ts @@ -3,7 +3,7 @@ import { makeMap } from './makeMap' const GLOBALS_ALLOWED = 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' + 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' + - 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console' + 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error' export const isGloballyAllowed = /*#__PURE__*/ makeMap(GLOBALS_ALLOWED) From f8b74dcf291bbcad996e2cabf03fb9323aac786b Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 30 Nov 2023 22:39:55 +0800 Subject: [PATCH 4/4] chore: group parser edge case tests --- .../__snapshots__/parse.spec.ts.snap | 992 +++++++++--------- .../compiler-core/__tests__/parse.spec.ts | 295 +++--- 2 files changed, 644 insertions(+), 643 deletions(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 924038450..8886ce337 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -1,5 +1,501 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`compiler: parse > Edge Cases > invalid html 1`] = ` +{ + "cached": 0, + "children": [ + { + "children": [ + { + "children": [], + "codegenNode": undefined, + "loc": { + "end": { + "column": 1, + "line": 3, + "offset": 13, + }, + "source": " +", + "start": { + "column": 1, + "line": 2, + "offset": 6, + }, + }, + "ns": 0, + "props": [], + "tag": "span", + "tagType": 0, + "type": 1, + }, + ], + "codegenNode": undefined, + "loc": { + "end": { + "column": 7, + "line": 3, + "offset": 19, + }, + "source": "
+ +
", + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "ns": 0, + "props": [], + "tag": "div", + "tagType": 0, + "type": 1, + }, + ], + "codegenNode": undefined, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 8, + "line": 4, + "offset": 27, + }, + "source": "
+ +
+
", + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "source": "
+ +
+", + "temps": 0, + "type": 0, +} +`; + +exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = ` +{ + "cached": 0, + "children": [ + { + "children": [], + "codegenNode": undefined, + "isSelfClosing": true, + "loc": { + "end": { + "column": 37, + "line": 1, + "offset": 36, + }, + "source": "
", + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "ns": 0, + "props": [ + { + "arg": { + "constType": 3, + "content": "class", + "isStatic": true, + "loc": { + "end": { + "column": 12, + "line": 1, + "offset": 11, + }, + "source": "class", + "start": { + "column": 7, + "line": 1, + "offset": 6, + }, + }, + "type": 4, + }, + "exp": { + "constType": 0, + "content": "{ some: condition }", + "isStatic": false, + "loc": { + "end": { + "column": 33, + "line": 1, + "offset": 32, + }, + "source": "{ some: condition }", + "start": { + "column": 14, + "line": 1, + "offset": 13, + }, + }, + "type": 4, + }, + "loc": { + "end": { + "column": 34, + "line": 1, + "offset": 33, + }, + "source": ":class=\\"{ some: condition }\\"", + "start": { + "column": 6, + "line": 1, + "offset": 5, + }, + }, + "modifiers": [], + "name": "bind", + "rawName": ":class", + "type": 7, + }, + ], + "tag": "div", + "tagType": 0, + "type": 1, + }, + { + "children": [], + "codegenNode": undefined, + "isSelfClosing": true, + "loc": { + "end": { + "column": 37, + "line": 2, + "offset": 73, + }, + "source": "

", + "start": { + "column": 1, + "line": 2, + "offset": 37, + }, + }, + "ns": 0, + "props": [ + { + "arg": { + "constType": 3, + "content": "style", + "isStatic": true, + "loc": { + "end": { + "column": 16, + "line": 2, + "offset": 52, + }, + "source": "style", + "start": { + "column": 11, + "line": 2, + "offset": 47, + }, + }, + "type": 4, + }, + "exp": { + "constType": 0, + "content": "{ color: 'red' }", + "isStatic": false, + "loc": { + "end": { + "column": 34, + "line": 2, + "offset": 70, + }, + "source": "{ color: 'red' }", + "start": { + "column": 18, + "line": 2, + "offset": 54, + }, + }, + "type": 4, + }, + "loc": { + "end": { + "column": 35, + "line": 2, + "offset": 71, + }, + "source": "v-bind:style=\\"{ color: 'red' }\\"", + "start": { + "column": 4, + "line": 2, + "offset": 40, + }, + }, + "modifiers": [], + "name": "bind", + "rawName": "v-bind:style", + "type": 7, + }, + ], + "tag": "p", + "tagType": 0, + "type": 1, + }, + ], + "codegenNode": undefined, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 37, + "line": 2, + "offset": 73, + }, + "source": "

+

", + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "source": "

+

", + "temps": 0, + "type": 0, +} +`; + +exports[`compiler: parse > Edge Cases > valid html 1`] = ` +{ + "cached": 0, + "children": [ + { + "children": [ + { + "children": [], + "codegenNode": undefined, + "isSelfClosing": true, + "loc": { + "end": { + "column": 39, + "line": 2, + "offset": 73, + }, + "source": "

", + "start": { + "column": 3, + "line": 2, + "offset": 37, + }, + }, + "ns": 0, + "props": [ + { + "arg": { + "constType": 3, + "content": "style", + "isStatic": true, + "loc": { + "end": { + "column": 18, + "line": 2, + "offset": 52, + }, + "source": "style", + "start": { + "column": 13, + "line": 2, + "offset": 47, + }, + }, + "type": 4, + }, + "exp": { + "constType": 0, + "content": "{ color: 'red' }", + "isStatic": false, + "loc": { + "end": { + "column": 36, + "line": 2, + "offset": 70, + }, + "source": "{ color: 'red' }", + "start": { + "column": 20, + "line": 2, + "offset": 54, + }, + }, + "type": 4, + }, + "loc": { + "end": { + "column": 37, + "line": 2, + "offset": 71, + }, + "source": "v-bind:style=\\"{ color: 'red' }\\"", + "start": { + "column": 6, + "line": 2, + "offset": 40, + }, + }, + "modifiers": [], + "name": "bind", + "rawName": "v-bind:style", + "type": 7, + }, + ], + "tag": "p", + "tagType": 0, + "type": 1, + }, + { + "content": " a comment with inside it ", + "loc": { + "end": { + "column": 43, + "line": 3, + "offset": 116, + }, + "source": "", + "start": { + "column": 3, + "line": 3, + "offset": 76, + }, + }, + "type": 3, + }, + ], + "codegenNode": undefined, + "loc": { + "end": { + "column": 7, + "line": 4, + "offset": 123, + }, + "source": "

+

+ +

", + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "ns": 0, + "props": [ + { + "arg": { + "constType": 3, + "content": "class", + "isStatic": true, + "loc": { + "end": { + "column": 12, + "line": 1, + "offset": 11, + }, + "source": "class", + "start": { + "column": 7, + "line": 1, + "offset": 6, + }, + }, + "type": 4, + }, + "exp": { + "constType": 0, + "content": "{ some: condition }", + "isStatic": false, + "loc": { + "end": { + "column": 33, + "line": 1, + "offset": 32, + }, + "source": "{ some: condition }", + "start": { + "column": 14, + "line": 1, + "offset": 13, + }, + }, + "type": 4, + }, + "loc": { + "end": { + "column": 34, + "line": 1, + "offset": 33, + }, + "source": ":class=\\"{ some: condition }\\"", + "start": { + "column": 6, + "line": 1, + "offset": 5, + }, + }, + "modifiers": [], + "name": "bind", + "rawName": ":class", + "type": 7, + }, + ], + "tag": "div", + "tagType": 0, + "type": 1, + }, + ], + "codegenNode": undefined, + "components": [], + "directives": [], + "helpers": Set {}, + "hoists": [], + "imports": [], + "loc": { + "end": { + "column": 7, + "line": 4, + "offset": 123, + }, + "source": "
+

+ +

", + "start": { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "source": "
+

+ +

", + "temps": 0, + "type": 0, +} +`; + exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > 1`] = ` { "cached": 0, @@ -4326,499 +4822,3 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{}} 1`] = ` "type": 0, } `; - -exports[`compiler: parse > invalid html 1`] = ` -{ - "cached": 0, - "children": [ - { - "children": [ - { - "children": [], - "codegenNode": undefined, - "loc": { - "end": { - "column": 1, - "line": 3, - "offset": 13, - }, - "source": " -", - "start": { - "column": 1, - "line": 2, - "offset": 6, - }, - }, - "ns": 0, - "props": [], - "tag": "span", - "tagType": 0, - "type": 1, - }, - ], - "codegenNode": undefined, - "loc": { - "end": { - "column": 7, - "line": 3, - "offset": 19, - }, - "source": "
- -
", - "start": { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": [], - "tag": "div", - "tagType": 0, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": [], - "directives": [], - "helpers": Set {}, - "hoists": [], - "imports": [], - "loc": { - "end": { - "column": 8, - "line": 4, - "offset": 27, - }, - "source": "
- -
-
", - "start": { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "source": "
- -
-", - "temps": 0, - "type": 0, -} -`; - -exports[`compiler: parse > self closing multiple tag 1`] = ` -{ - "cached": 0, - "children": [ - { - "children": [], - "codegenNode": undefined, - "isSelfClosing": true, - "loc": { - "end": { - "column": 37, - "line": 1, - "offset": 36, - }, - "source": "
", - "start": { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": [ - { - "arg": { - "constType": 3, - "content": "class", - "isStatic": true, - "loc": { - "end": { - "column": 12, - "line": 1, - "offset": 11, - }, - "source": "class", - "start": { - "column": 7, - "line": 1, - "offset": 6, - }, - }, - "type": 4, - }, - "exp": { - "constType": 0, - "content": "{ some: condition }", - "isStatic": false, - "loc": { - "end": { - "column": 33, - "line": 1, - "offset": 32, - }, - "source": "{ some: condition }", - "start": { - "column": 14, - "line": 1, - "offset": 13, - }, - }, - "type": 4, - }, - "loc": { - "end": { - "column": 34, - "line": 1, - "offset": 33, - }, - "source": ":class=\\"{ some: condition }\\"", - "start": { - "column": 6, - "line": 1, - "offset": 5, - }, - }, - "modifiers": [], - "name": "bind", - "rawName": ":class", - "type": 7, - }, - ], - "tag": "div", - "tagType": 0, - "type": 1, - }, - { - "children": [], - "codegenNode": undefined, - "isSelfClosing": true, - "loc": { - "end": { - "column": 37, - "line": 2, - "offset": 73, - }, - "source": "

", - "start": { - "column": 1, - "line": 2, - "offset": 37, - }, - }, - "ns": 0, - "props": [ - { - "arg": { - "constType": 3, - "content": "style", - "isStatic": true, - "loc": { - "end": { - "column": 16, - "line": 2, - "offset": 52, - }, - "source": "style", - "start": { - "column": 11, - "line": 2, - "offset": 47, - }, - }, - "type": 4, - }, - "exp": { - "constType": 0, - "content": "{ color: 'red' }", - "isStatic": false, - "loc": { - "end": { - "column": 34, - "line": 2, - "offset": 70, - }, - "source": "{ color: 'red' }", - "start": { - "column": 18, - "line": 2, - "offset": 54, - }, - }, - "type": 4, - }, - "loc": { - "end": { - "column": 35, - "line": 2, - "offset": 71, - }, - "source": "v-bind:style=\\"{ color: 'red' }\\"", - "start": { - "column": 4, - "line": 2, - "offset": 40, - }, - }, - "modifiers": [], - "name": "bind", - "rawName": "v-bind:style", - "type": 7, - }, - ], - "tag": "p", - "tagType": 0, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": [], - "directives": [], - "helpers": Set {}, - "hoists": [], - "imports": [], - "loc": { - "end": { - "column": 37, - "line": 2, - "offset": 73, - }, - "source": "

-

", - "start": { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "source": "

-

", - "temps": 0, - "type": 0, -} -`; - -exports[`compiler: parse > valid html 1`] = ` -{ - "cached": 0, - "children": [ - { - "children": [ - { - "children": [], - "codegenNode": undefined, - "isSelfClosing": true, - "loc": { - "end": { - "column": 39, - "line": 2, - "offset": 73, - }, - "source": "

", - "start": { - "column": 3, - "line": 2, - "offset": 37, - }, - }, - "ns": 0, - "props": [ - { - "arg": { - "constType": 3, - "content": "style", - "isStatic": true, - "loc": { - "end": { - "column": 18, - "line": 2, - "offset": 52, - }, - "source": "style", - "start": { - "column": 13, - "line": 2, - "offset": 47, - }, - }, - "type": 4, - }, - "exp": { - "constType": 0, - "content": "{ color: 'red' }", - "isStatic": false, - "loc": { - "end": { - "column": 36, - "line": 2, - "offset": 70, - }, - "source": "{ color: 'red' }", - "start": { - "column": 20, - "line": 2, - "offset": 54, - }, - }, - "type": 4, - }, - "loc": { - "end": { - "column": 37, - "line": 2, - "offset": 71, - }, - "source": "v-bind:style=\\"{ color: 'red' }\\"", - "start": { - "column": 6, - "line": 2, - "offset": 40, - }, - }, - "modifiers": [], - "name": "bind", - "rawName": "v-bind:style", - "type": 7, - }, - ], - "tag": "p", - "tagType": 0, - "type": 1, - }, - { - "content": " a comment with inside it ", - "loc": { - "end": { - "column": 43, - "line": 3, - "offset": 116, - }, - "source": "", - "start": { - "column": 3, - "line": 3, - "offset": 76, - }, - }, - "type": 3, - }, - ], - "codegenNode": undefined, - "loc": { - "end": { - "column": 7, - "line": 4, - "offset": 123, - }, - "source": "

-

- -

", - "start": { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "ns": 0, - "props": [ - { - "arg": { - "constType": 3, - "content": "class", - "isStatic": true, - "loc": { - "end": { - "column": 12, - "line": 1, - "offset": 11, - }, - "source": "class", - "start": { - "column": 7, - "line": 1, - "offset": 6, - }, - }, - "type": 4, - }, - "exp": { - "constType": 0, - "content": "{ some: condition }", - "isStatic": false, - "loc": { - "end": { - "column": 33, - "line": 1, - "offset": 32, - }, - "source": "{ some: condition }", - "start": { - "column": 14, - "line": 1, - "offset": 13, - }, - }, - "type": 4, - }, - "loc": { - "end": { - "column": 34, - "line": 1, - "offset": 33, - }, - "source": ":class=\\"{ some: condition }\\"", - "start": { - "column": 6, - "line": 1, - "offset": 5, - }, - }, - "modifiers": [], - "name": "bind", - "rawName": ":class", - "type": 7, - }, - ], - "tag": "div", - "tagType": 0, - "type": 1, - }, - ], - "codegenNode": undefined, - "components": [], - "directives": [], - "helpers": Set {}, - "hoists": [], - "imports": [], - "loc": { - "end": { - "column": 7, - "line": 4, - "offset": 123, - }, - "source": "
-

- -

", - "start": { - "column": 1, - "line": 1, - "offset": 0, - }, - }, - "source": "
-

- -

", - "temps": 0, - "type": 0, -} -`; diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 4e6c80b38..05a2afcdc 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1796,166 +1796,167 @@ describe('compiler: parse', () => { }) }) - test('self closing single tag', () => { - const ast = baseParse('
') + describe('Edge Cases', () => { + test('self closing single tag', () => { + const ast = baseParse('
') - expect(ast.children).toHaveLength(1) - expect(ast.children[0]).toMatchObject({ tag: 'div' }) - }) - - test('self closing multiple tag', () => { - const ast = baseParse( - `
\n` + - `

` - ) - - expect(ast).toMatchSnapshot() - - expect(ast.children).toHaveLength(2) - expect(ast.children[0]).toMatchObject({ tag: 'div' }) - expect(ast.children[1]).toMatchObject({ tag: 'p' }) - }) - - test('valid html', () => { - const ast = baseParse( - `

\n` + - `

\n` + - ` \n` + - `

` - ) - - expect(ast).toMatchSnapshot() - - expect(ast.children).toHaveLength(1) - const el = ast.children[0] as any - expect(el).toMatchObject({ - tag: 'div' - }) - expect(el.children).toHaveLength(2) - expect(el.children[0]).toMatchObject({ - tag: 'p' - }) - expect(el.children[1]).toMatchObject({ - type: NodeTypes.COMMENT - }) - }) - - test('invalid html', () => { - expect(() => { - baseParse(`
\n\n
\n`) - }).toThrow('Element is missing end tag.') - - const spy = vi.fn() - const ast = baseParse(`
\n\n
\n`, { - onError: spy + expect(ast.children).toHaveLength(1) + expect(ast.children[0]).toMatchObject({ tag: 'div' }) }) - expect(spy.mock.calls).toMatchObject([ - [ - { - code: ErrorCodes.X_MISSING_END_TAG, - loc: { - start: { - offset: 6, - line: 2, - column: 1 - } - } - } - ], - [ - { - code: ErrorCodes.X_INVALID_END_TAG, - loc: { - start: { - offset: 20, - line: 4, - column: 1 - } - } - } - ] - ]) + test('self closing multiple tag', () => { + const ast = baseParse( + `
\n` + + `

` + ) - expect(ast).toMatchSnapshot() - }) + expect(ast).toMatchSnapshot() - test('parse with correct location info', () => { - const fooSrc = `foo - is ` - const barSrc = `{{ bar }}` - const butSrc = ` but ` - const bazSrc = `{{ baz }}` - const [foo, bar, but, baz] = baseParse( - fooSrc + barSrc + butSrc + bazSrc - ).children + expect(ast.children).toHaveLength(2) + expect(ast.children[0]).toMatchObject({ tag: 'div' }) + expect(ast.children[1]).toMatchObject({ tag: 'p' }) + }) - let offset = 0 - expect(foo.loc.start).toEqual({ line: 1, column: 1, offset }) - offset += fooSrc.length - expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) + test('valid html', () => { + const ast = baseParse( + `

\n` + + `

\n` + + ` \n` + + `

` + ) - expect(bar.loc.start).toEqual({ line: 2, column: 5, offset }) - const barInner = (bar as InterpolationNode).content - offset += 3 - expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset }) - offset += 3 - expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset }) - offset += 3 - expect(bar.loc.end).toEqual({ line: 2, column: 14, offset }) + expect(ast).toMatchSnapshot() - expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) - offset += butSrc.length - expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) + expect(ast.children).toHaveLength(1) + const el = ast.children[0] as any + expect(el).toMatchObject({ + tag: 'div' + }) + expect(el.children).toHaveLength(2) + expect(el.children[0]).toMatchObject({ + tag: 'p' + }) + expect(el.children[1]).toMatchObject({ + type: NodeTypes.COMMENT + }) + }) - expect(baz.loc.start).toEqual({ line: 2, column: 19, offset }) - const bazInner = (baz as InterpolationNode).content - offset += 3 - expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset }) - offset += 3 - expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset }) - offset += 3 - expect(baz.loc.end).toEqual({ line: 2, column: 28, offset }) - }) + test('invalid html', () => { + expect(() => { + baseParse(`
\n\n
\n`) + }).toThrow('Element is missing end tag.') - // With standard HTML parsing, the following input would ignore the slash - // and treat "<" and "template" as attributes on the open tag of "Hello", - // causing `