fix: use strip-literal to strip string lterals (#8054)

This commit is contained in:
Anthony Fu 2022-05-08 12:39:34 +08:00
parent b0e9234251
commit b6fc3cdccf
10 changed files with 49 additions and 339 deletions

View File

@ -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 <https://github.com/antfu>
>
> 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

View File

@ -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",

View File

@ -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 =
"<img src=\"${new URL('../assets/images/loading/loading.gif', import.meta.url).href}\" alt=''>"
res = `<img src="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" alt=''>`
clean = emptyString(str)
expect(clean).toMatch(res)
})

View File

@ -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
}

View File

@ -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))) {

View File

@ -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')

View File

@ -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))) {

View File

@ -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

View File

@ -785,3 +785,7 @@ function gracefulRename(
if (cb) cb(er)
})
}
export function emptyCssComments(raw: string) {
return raw.replace(multilineCommentsRE, (s) => ' '.repeat(s.length))
}

View File

@ -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==}