Skip to content

Commit

Permalink
refactor: use function to eval worker and glob options (#10999)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy committed Nov 30, 2022
1 parent 1ec0176 commit f4c1264
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 138 deletions.
31 changes: 0 additions & 31 deletions packages/vite/LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1832,37 +1832,6 @@ Repository: git+https://github.com/isaacs/isexe.git
---------------------------------------

## json5
License: MIT
By: Aseem Kishore, Max Nanasy, Andrew Eisenberg, Jordan Tucker
Repository: git+https://github.com/json5/json5.git

> MIT License
>
> Copyright (c) 2012-2018 Aseem Kishore, and [others].
>
> 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.
>
> [others]: https://github.com/json5/json5/contributors
---------------------------------------

## launch-editor
License: MIT
By: Evan You
Expand Down
1 change: 0 additions & 1 deletion packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@
"etag": "^1.8.1",
"fast-glob": "^3.2.12",
"http-proxy": "^1.18.1",
"json5": "^2.2.1",
"launch-editor-middleware": "^2.6.0",
"magic-string": "^0.26.7",
"micromatch": "^4.0.5",
Expand Down
22 changes: 10 additions & 12 deletions packages/vite/src/node/__tests__/plugins/importGlob/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,58 +327,56 @@ describe('parse negatives', async () => {
expect(
await runError('import.meta.glob("hey", hey)')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Expected the second argument o to be a object literal, but got "Identifier"]'
'[Error: Invalid glob import syntax: Expected the second argument to be an object literal, but got "Identifier"]'
)
expect(await runError('import.meta.glob("hey", [])')).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Expected the second argument o to be a object literal, but got "ArrayExpression"]'
'[Error: Invalid glob import syntax: Expected the second argument to be an object literal, but got "ArrayExpression"]'
)
})

it('options props', async () => {
expect(
await runError('import.meta.glob("hey", { hey: 1 })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Unknown options hey]'
)
).toMatchInlineSnapshot('[Error: Unknown glob option "hey"]')
expect(
await runError('import.meta.glob("hey", { import: hey })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Could only use literals]'
'[Error: Vite is unable to parse the glob options as the value is not static]'
)
expect(
await runError('import.meta.glob("hey", { eager: 123 })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Expected the type of option "eager" to be "boolean", but got "number"]'
'[Error: Expected glob option "eager" to be of type boolean, but got number]'
)
})

it('options query', async () => {
expect(
await runError('import.meta.glob("./*.js", { as: "raw", query: "hi" })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Options "as" and "query" cannot be used together]'
'[Error: Options "as" and "query" cannot be used together]'
)
expect(
await runError('import.meta.glob("./*.js", { query: 123 })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Expected query to be a string, but got "number"]'
'[Error: Expected glob option "query" to be of type object or string, but got number]'
)
expect(
await runError('import.meta.glob("./*.js", { query: { foo: {} } })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Could only use literals]'
'[Error: Expected glob option "query.foo" to be of type string, number, or boolean, but got object]'
)
expect(
await runError('import.meta.glob("./*.js", { query: { foo: hey } })')
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Could only use literals]'
'[Error: Vite is unable to parse the glob options as the value is not static]'
)
expect(
await runError(
'import.meta.glob("./*.js", { query: { foo: 123, ...a } })'
)
).toMatchInlineSnapshot(
'[Error: Invalid glob import syntax: Could only use literals]'
'[Error: Vite is unable to parse the glob options as the value is not static]'
)
})
})
158 changes: 85 additions & 73 deletions packages/vite/src/node/plugins/importMetaGlob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
TemplateLiteral
} from 'estree'
import { parseExpressionAt } from 'acorn'
import type { RollupError } from 'rollup'
import { findNodeAt } from 'acorn-walk'
import MagicString from 'magic-string'
import fg from 'fast-glob'
Expand All @@ -24,6 +25,7 @@ import type { ViteDevServer } from '../server'
import type { ModuleNode } from '../server/moduleGraph'
import type { ResolvedConfig } from '../config'
import {
evalValue,
generateCodeFrame,
normalizePath,
slash,
Expand Down Expand Up @@ -95,14 +97,87 @@ const importGlobRE =
/\bimport\.meta\.(glob|globEager|globEagerDefault)(?:<\w+>)?\s*\(/g

const knownOptions = {
as: 'string',
eager: 'boolean',
import: 'string',
exhaustive: 'boolean'
} as const
as: ['string'],
eager: ['boolean'],
import: ['string'],
exhaustive: ['boolean'],
query: ['object', 'string']
}

const forceDefaultAs = ['raw', 'url']

function err(e: string, pos: number) {
const error = new Error(e) as RollupError
error.pos = pos
return error
}

function parseGlobOptions(
rawOpts: string,
optsStartIndex: number
): GeneralImportGlobOptions {
let opts: GeneralImportGlobOptions = {}
try {
opts = evalValue(rawOpts)
} catch {
throw err(
'Vite is unable to parse the glob options as the value is not static',
optsStartIndex
)
}

if (opts == null) {
return {}
}

for (const key in opts) {
if (!(key in knownOptions)) {
throw err(`Unknown glob option "${key}"`, optsStartIndex)
}
const allowedTypes = knownOptions[key as keyof typeof knownOptions]
const valueType = typeof opts[key as keyof GeneralImportGlobOptions]
if (!allowedTypes.includes(valueType)) {
throw err(
`Expected glob option "${key}" to be of type ${allowedTypes.join(
' or '
)}, but got ${valueType}`,
optsStartIndex
)
}
}

if (typeof opts.query === 'object') {
for (const key in opts.query) {
const value = opts.query[key]
if (!['string', 'number', 'boolean'].includes(typeof value)) {
throw err(
`Expected glob option "query.${key}" to be of type string, number, or boolean, but got ${typeof value}`,
optsStartIndex
)
}
}
}

if (opts.as && forceDefaultAs.includes(opts.as)) {
if (opts.import && opts.import !== 'default' && opts.import !== '*')
throw err(
`Option "import" can only be "default" or "*" when "as" is "${opts.as}", but got "${opts.import}"`,
optsStartIndex
)
opts.import = opts.import || 'default'
}

if (opts.as && opts.query)
throw err(
'Options "as" and "query" cannot be used together',
optsStartIndex
)

if (opts.as) opts.query = opts.as

return opts
}

export async function parseImportGlob(
code: string,
importer: string | undefined,
Expand Down Expand Up @@ -205,82 +280,19 @@ export async function parseImportGlob(
}

// arg2
const options: GeneralImportGlobOptions = {}
let options: GeneralImportGlobOptions = {}
if (arg2) {
if (arg2.type !== 'ObjectExpression')
throw err(
`Expected the second argument o to be a object literal, but got "${arg2.type}"`
)

for (const property of arg2.properties) {
if (
property.type === 'SpreadElement' ||
(property.key.type !== 'Identifier' &&
property.key.type !== 'Literal')
`Expected the second argument to be an object literal, but got "${arg2.type}"`
)
throw err('Could only use literals')

const name = ((property.key as any).name ||
(property.key as any).value) as keyof GeneralImportGlobOptions
if (name === 'query') {
if (property.value.type === 'ObjectExpression') {
const data: Record<string, string> = {}
for (const prop of property.value.properties) {
if (
prop.type === 'SpreadElement' ||
prop.key.type !== 'Identifier' ||
prop.value.type !== 'Literal'
)
throw err('Could only use literals')
data[prop.key.name] = prop.value.value as any
}
options.query = data
} else if (property.value.type === 'Literal') {
if (typeof property.value.value !== 'string')
throw err(
`Expected query to be a string, but got "${typeof property.value
.value}"`
)
options.query = property.value.value
} else {
throw err('Could only use literals')
}
continue
}

if (!(name in knownOptions)) throw err(`Unknown options ${name}`)

if (property.value.type !== 'Literal')
throw err('Could only use literals')

const valueType = typeof property.value.value
if (valueType === 'undefined') continue

if (valueType !== knownOptions[name])
throw err(
`Expected the type of option "${name}" to be "${knownOptions[name]}", but got "${valueType}"`
)
options[name] = property.value.value as any
}
}

if (options.as && forceDefaultAs.includes(options.as)) {
if (
options.import &&
options.import !== 'default' &&
options.import !== '*'
options = parseGlobOptions(
code.slice(arg2.range![0], arg2.range![1]),
arg2.range![0]
)
throw err(
`Option "import" can only be "default" or "*" when "as" is "${options.as}", but got "${options.import}"`
)
options.import = options.import || 'default'
}

if (options.as && options.query)
throw err('Options "as" and "query" cannot be used together')

if (options.as) options.query = options.as

const end = ast.range![1]

const globsResolved = await Promise.all(
Expand Down

0 comments on commit f4c1264

Please sign in to comment.