Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use function to eval worker and glob options #10999

Merged
merged 6 commits into from Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 0 additions & 31 deletions packages/vite/LICENSE.md
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
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
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
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