From b6fc3cdccfb3ed0d891d3c998f7db462bd814fd4 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 8 May 2022 12:39:34 +0800 Subject: [PATCH] fix: use `strip-literal` to strip string lterals (#8054) --- packages/vite/LICENSE.md | 29 +++ packages/vite/package.json | 1 + .../src/node/__tests__/cleanString.spec.ts | 190 ------------------ packages/vite/src/node/cleanString.ts | 142 ------------- .../src/node/plugins/assetImportMetaUrl.ts | 4 +- packages/vite/src/node/plugins/css.ts | 2 +- packages/vite/src/node/plugins/html.ts | 4 +- .../src/node/plugins/workerImportMetaUrl.ts | 4 +- packages/vite/src/node/utils.ts | 4 + pnpm-lock.yaml | 8 + 10 files changed, 49 insertions(+), 339 deletions(-) delete mode 100644 packages/vite/src/node/__tests__/cleanString.spec.ts delete mode 100644 packages/vite/src/node/cleanString.ts diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md index 30803708e30a76..80feb7f95c6941 100644 --- a/packages/vite/LICENSE.md +++ b/packages/vite/LICENSE.md @@ -3465,6 +3465,35 @@ Repository: chalk/strip-ansi --------------------------------------- +## strip-literal +License: MIT +By: Anthony Fu +Repository: git+https://github.com/antfu/strip-literal.git + +> MIT License +> +> Copyright (c) 2022 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + ## to-regex-range License: MIT By: Jon Schlinkert, Rouven Weßling diff --git a/packages/vite/package.json b/packages/vite/package.json index 07d0025c449e75..3c96a3cb56a012 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -112,6 +112,7 @@ "source-map-js": "^1.0.2", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", + "strip-literal": "^0.2.0", "terser": "^5.13.1", "tsconfck": "^1.2.2", "tslib": "^2.4.0", diff --git a/packages/vite/src/node/__tests__/cleanString.spec.ts b/packages/vite/src/node/__tests__/cleanString.spec.ts deleted file mode 100644 index f307c4816b7cd3..00000000000000 --- a/packages/vite/src/node/__tests__/cleanString.spec.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { assetAttrsConfig } from './../plugins/html' -import { emptyString } from '../../node/cleanString' - -test('comments', () => { - expect( - emptyString(` - // comment1 // comment - // comment1 - /* coment2 */ - /* - // coment3 - */ - /* // coment3 */ - /* // coment3 */ // comment - // comment 4 /* comment 5 */ - `).trim() - ).toBe('') -}) - -test('strings', () => { - const clean = emptyString(` - // comment1 - const a = 'aaaa' - /* coment2 */ - const b = "bbbb" - /* - // coment3 - */ - /* // coment3 */ - // comment 4 /* comment 5 */ - `) - expect(clean).toMatch("const a = '\0\0\0\0'") - expect(clean).toMatch('const b = "\0\0\0\0"') -}) - -test('escape character', () => { - const clean = emptyString(` - '1\\'1' - "1\\"1" - "1\\"1\\"1" - "1\\'1'\\"1" - "1'1'" - "1'\\'1\\''\\"1\\"\\"" - '1"\\"1\\""\\"1\\"\\"' - '""1""' - '"""1"""' - '""""1""""' - "''1''" - "'''1'''" - "''''1''''" - `) - expect(clean).not.toMatch('1') -}) - -test('regexp affect', () => { - const clean = emptyString(` - /'/ - '1' - /"/ - "1" - `) - expect(clean).not.toMatch('1') -}) - -test('strings comment nested', () => { - expect( - emptyString(` - // comment 1 /* " */ - const a = "a //" - // comment 2 /* " */ - `) - ).toMatch('const a = "\0\0\0\0"') - - expect( - emptyString(` - // comment 1 /* ' */ - const a = "a //" - // comment 2 /* ' */ - `) - ).toMatch('const a = "\0\0\0\0"') - - expect( - emptyString(` - // comment 1 /* \` */ - const a = "a //" - // comment 2 /* \` */ - `) - ).toMatch('const a = "\0\0\0\0"') - - expect( - emptyString(` - const a = "a //" - console.log("console") - `) - ).toMatch('const a = "\0\0\0\0"') - - expect( - emptyString(` - const a = "a /*" - console.log("console") - const b = "b */" - `) - ).toMatch('const a = "\0\0\0\0"') - - expect( - emptyString(` - const a = "a ' " - console.log("console") - const b = "b ' " - `) - ).toMatch('const a = "\0\0\0\0"') - - expect( - emptyString(` - const a = "a \` " - console.log("console") - const b = "b \` " - `) - ).toMatch('const a = "\0\0\0\0"') -}) - -test('find empty string flag in raw index', () => { - const str = ` - const a = "aaaaa" - const b = "bbbbb" - ` - const clean = emptyString(str) - expect(clean).toMatch('const a = "\0\0\0\0\0"') - expect(clean).toMatch('const b = "\0\0\0\0\0"') - - const aIndex = str.indexOf('const a = "aaaaa"') - const aStart = clean.indexOf('\0\0\0\0\0', aIndex) - expect(str.slice(aStart, aStart + 5)).toMatch('aaaaa') - - const bIndex = str.indexOf('const b = "bbbbb"') - const bStart = clean.indexOf('\0\0\0\0\0', bIndex) - expect(str.slice(bStart, bStart + 5)).toMatch('bbbbb') -}) - -test('template string nested', () => { - let str = '`aaaa`' - let res = '`\0\0\0\0`' - let clean = emptyString(str) - expect(clean).toMatch(res) - - str = '`aaaa` `aaaa`' - res = '`\0\0\0\0` `\0\0\0\0`' - clean = emptyString(str) - expect(clean).toMatch(res) - - str = '`aa${a}aa`' - res = '`\0\0${a}\0\0`' - clean = emptyString(str) - expect(clean).toMatch(res) - - str = '`aa${a + `a` + a}aa`' - res = '`\0\0${a + `\0` + a}\0\0`' - clean = emptyString(str) - expect(clean).toMatch(res) - - str = '`aa${a + `a` + a}aa` `aa${a + `a` + a}aa`' - res = '`\0\0${a + `\0` + a}\0\0` `\0\0${a + `\0` + a}\0\0`' - clean = emptyString(str) - expect(clean).toMatch(res) - - str = '`aa${a + `aaaa${c + (a = {b: 1}) + d}` + a}aa`' - res = '`\0\0${a + `\0\0\0\0${c + (a = {b: 1}) + d}` + a}\0\0`' - clean = emptyString(str) - expect(clean).toMatch(res) - - str = - '`aa${a + `aaaa${c + (a = {b: 1}) + d}` + a}aa` `aa${a + `aaaa${c + (a = {b: 1}) + d}` + a}aa`' - res = - '`\0\0${a + `\0\0\0\0${c + (a = {b: 1}) + d}` + a}\0\0` `\0\0${a + `\0\0\0\0${c + (a = {b: 1}) + d}` + a}\0\0`' - clean = emptyString(str) - expect(clean).toMatch(res) - - str = '`aaaa' - res = '' - try { - clean = emptyString(str) - } catch {} - expect(clean).toMatch(res) - - str = - "" - res = `` - clean = emptyString(str) - expect(clean).toMatch(res) -}) diff --git a/packages/vite/src/node/cleanString.ts b/packages/vite/src/node/cleanString.ts deleted file mode 100644 index 9b9ef656f4e017..00000000000000 --- a/packages/vite/src/node/cleanString.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { RollupError } from 'rollup' -import { multilineCommentsRE, singlelineCommentsRE } from './utils' - -// bank on the non-overlapping nature of regex matches and combine all filters into one giant regex -// /`([^`\$\{\}]|\$\{(`|\g<1>)*\})*`/g can match nested string template -// but js not support match expression(\g<0>). so clean string template(`...`) in other ways. -const stringsRE = /"([^"\r\n]|(?<=\\)")*"|'([^'\r\n]|(?<=\\)')*'/g -const cleanerRE = new RegExp( - `${stringsRE.source}|${multilineCommentsRE.source}|${singlelineCommentsRE.source}`, - 'g' -) - -const blankReplacer = (s: string) => ' '.repeat(s.length) -const stringBlankReplacer = (s: string) => - `${s[0]}${'\0'.repeat(s.length - 2)}${s[0]}` - -export function emptyString(raw: string): string { - let res = raw.replace(cleanerRE, (s: string) => - s[0] === '/' ? blankReplacer(s) : stringBlankReplacer(s) - ) - - let lastEnd = 0 - let start = 0 - while ((start = res.indexOf('`', lastEnd)) >= 0) { - let clean - ;[clean, lastEnd] = lexStringTemplateExpression(res, start) - res = replaceAt(res, start, lastEnd, clean) - } - - return res -} - -export function emptyCssComments(raw: string) { - return raw.replace(multilineCommentsRE, blankReplacer) -} - -const enum LexerState { - // template string - inTemplateString, - inInterpolationExpression, - inObjectExpression, - // strings - inSingleQuoteString, - inDoubleQuoteString, - // comments - inMultilineCommentsRE, - inSinglelineCommentsRE -} - -function replaceAt( - string: string, - start: number, - end: number, - replacement: string -): string { - return string.slice(0, start) + replacement + string.slice(end) -} - -/** - * lex string template and clean it. - */ -function lexStringTemplateExpression( - code: string, - start: number -): [string, number] { - let state = LexerState.inTemplateString as LexerState - let clean = '`' - const opStack: LexerState[] = [state] - - function pushStack(newState: LexerState) { - state = newState - opStack.push(state) - } - - function popStack() { - opStack.pop() - state = opStack[opStack.length - 1] - } - - let i = start + 1 - outer: for (; i < code.length; i++) { - const char = code.charAt(i) - switch (state) { - case LexerState.inTemplateString: - if (char === '$' && code.charAt(i + 1) === '{') { - pushStack(LexerState.inInterpolationExpression) - clean += '${' - i++ // jump next - } else if (char === '`') { - popStack() - clean += char - if (opStack.length === 0) { - break outer - } - } else { - clean += '\0' - } - break - case LexerState.inInterpolationExpression: - if (char === '{') { - pushStack(LexerState.inObjectExpression) - clean += char - } else if (char === '}') { - popStack() - clean += char - } else if (char === '`') { - pushStack(LexerState.inTemplateString) - clean += char - } else { - clean += char - } - break - case LexerState.inObjectExpression: - if (char === '}') { - popStack() - clean += char - } else if (char === '`') { - pushStack(LexerState.inTemplateString) - clean += char - } else { - clean += char - } - break - default: - throw new Error('unknown string template lexer state') - } - } - - if (opStack.length !== 0) { - error(start) - } - - return [clean, i + 1] -} - -function error(pos: number) { - const err = new Error( - `can not match string template expression.` - ) as RollupError - err.pos = pos - throw err -} diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index b8c16f76d2b93f..217b0d3fee1564 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -3,7 +3,7 @@ import MagicString from 'magic-string' import path from 'path' import { fileToUrl } from './asset' import type { ResolvedConfig } from '../config' -import { emptyString } from '../cleanString' +import { stripLiteral } from 'strip-literal' /** * Convert `new URL('./foo.png', import.meta.url)` to its resolved built URL @@ -27,7 +27,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { let s: MagicString | undefined const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*,?\s*\)/g - const cleanString = emptyString(code) + const cleanString = stripLiteral(code) let match: RegExpExecArray | null while ((match = assetImportMetaUrlRE.exec(cleanString))) { diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index cd57acd1690902..2715099a1cac3f 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -49,7 +49,7 @@ import { transform, formatMessages } from 'esbuild' import { addToHTMLProxyTransformResult } from './html' import { injectSourcesContent, getCodeWithSourcemap } from '../server/sourcemap' import type { RawSourceMap } from '@ampproject/remapping' -import { emptyCssComments } from '../cleanString' +import { emptyCssComments } from '../utils' // const debug = createDebugger('vite:css') diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index bf3d662066630b..998159a918f2a0 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -36,7 +36,7 @@ import type { TextNode } from '@vue/compiler-dom' import { NodeTypes } from '@vue/compiler-dom' -import { emptyString } from '../cleanString' +import { stripLiteral } from 'strip-literal' interface ScriptAssetsUrl { start: number @@ -307,7 +307,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { } } else if (node.children.length) { const scriptNode = node.children.pop()! as TextNode - const cleanCode = emptyString(scriptNode.content) + const cleanCode = stripLiteral(scriptNode.content) let match: RegExpExecArray | null while ((match = inlineImportRE.exec(cleanCode))) { diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 3d8970b746349c..bfd8c22dcad372 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -10,7 +10,7 @@ import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants' import MagicString from 'magic-string' import type { ViteDevServer } from '..' import type { RollupError } from 'rollup' -import { emptyString } from '../cleanString' +import { stripLiteral } from 'strip-literal' type WorkerType = 'classic' | 'module' | 'ignore' const ignoreFlagRE = /\/\*\s*@vite-ignore\s*\*\// @@ -110,7 +110,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { code.includes('new URL') && code.includes(`import.meta.url`) ) { - const cleanString = emptyString(code) + const cleanString = stripLiteral(code) const workerImportMetaUrlRE = /\bnew\s+(Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/g diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index d41dd6850ebb56..4234128591fc55 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -785,3 +785,7 @@ function gracefulRename( if (cb) cb(er) }) } + +export function emptyCssComments(raw: string) { + return raw.replace(multilineCommentsRE, (s) => ' '.repeat(s.length)) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57fa85d5aaacf7..a3255aa5b2c6ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -893,6 +893,7 @@ importers: source-map-js: ^1.0.2 source-map-support: ^0.5.21 strip-ansi: ^6.0.1 + strip-literal: ^0.2.0 terser: ^5.13.1 tsconfck: ^1.2.2 tslib: ^2.4.0 @@ -966,6 +967,7 @@ importers: source-map-js: 1.0.2 source-map-support: 0.5.21 strip-ansi: 6.0.1 + strip-literal: 0.2.0 terser: 5.13.1 tsconfck: 1.2.2_typescript@4.5.4 tslib: 2.4.0 @@ -8959,6 +8961,12 @@ packages: engines: {node: '>=8'} dev: true + /strip-literal/0.2.0: + resolution: {integrity: sha512-pqhiiFRDifA2CRVH0Gmv6MDbd2b27MS0oIqqy7JCqfL5m2sh68223lmEK2eoBXp4vNaq8G1Wzwd9dfQcWszUlg==} + dependencies: + acorn: 8.7.1 + dev: true + /stylis/4.0.13: resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}