\n foo bar`, { - isPreTag: tag => tag === 'pre' + isPreTag: tag => tag === 'pre', }) expect(ast.children).toHaveLength(1) const preElement = ast.children[0] as ElementNode @@ -2047,30 +2175,29 @@ foo expect((preElement.children[0] as TextNode).content).toBe(` foo bar `) }) - it('should NOT remove leading newline character immediately following child-tag of pre element', () => { + test('should NOT remove leading newline character immediately following child-tag of pre element', () => { const ast = baseParse(`
\n foo bar`, { - isPreTag: tag => tag === 'pre' + isPreTag: tag => tag === 'pre', }) const preElement = ast.children[0] as ElementNode expect(preElement.children).toHaveLength(2) expect((preElement.children[1] as TextNode).content).toBe( - `\n foo bar ` + `\n foo bar `, ) }) - it('self-closing pre tag', () => { + test('self-closing pre tag', () => { const ast = baseParse(`\n foo bar`, { - isPreTag: tag => tag === 'pre' + isPreTag: tag => tag === 'pre', }) const elementAfterPre = ast.children[1] as ElementNode // should not affect the and condense its whitespace inside expect((elementAfterPre.children[0] as TextNode).content).toBe(` foo bar`) }) - it('should NOT condense whitespaces in RCDATA text mode', () => { + test('should NOT condense whitespaces in RCDATA text mode', () => { const ast = baseParse(``, { - getTextMode: ({ tag }) => - tag === 'textarea' ? TextModes.RCDATA : TextModes.DATA + parseMode: 'html', }) const preElement = ast.children[0] as ElementNode expect(preElement.children).toHaveLength(1) @@ -2082,15 +2209,15 @@ foo const parse = (content: string, options?: ParserOptions) => baseParse(content, { whitespace: 'preserve', - ...options + ...options, }) - it('should still remove whitespaces at start/end inside an element', () => { + test('should still remove whitespaces at start/end inside an element', () => { const ast = parse(`
{{ o }}
{{ o + 'foo' }}
foo |
>() `, - files + files, ) expect(props).toStrictEqual({ - foo: ['String'] + foo: ['String'], }) }) }) @@ -544,7 +544,7 @@ describe('resolveType', () => { '/bar.d.ts': 'type X = { bar: string }; export { X as Y };' + // verify that we can parse syntax that is only valid in d.ts - 'export const baz: boolean' + 'export const baz: boolean', } const { props, deps } = resolve( ` @@ -552,11 +552,11 @@ describe('resolveType', () => { import { Y as PP } from './bar' defineProps
()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -568,7 +568,7 @@ describe('resolveType', () => {
'type X = { bar: string }; export { X as Y };' +
// verify that we can parse syntax that is only valid in d.ts
'export const baz: boolean',
- 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }'
+ 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }',
}
const { props, deps } = resolve(
`
@@ -579,32 +579,32 @@ describe('resolveType', () => {
`,
files,
{},
- 'C:\\Test\\FolderA\\Test.vue'
+ 'C:\\Test\\FolderA\\Test.vue',
)
expect(props).toStrictEqual({
foo: ['Number'],
bar: ['String'],
- buz: ['String']
+ buz: ['String'],
})
expect(deps && [...deps].map(normalize)).toStrictEqual(
- Object.keys(files).map(normalize)
+ Object.keys(files).map(normalize),
)
})
// #8244
test('utility type in external file', () => {
const files = {
- '/foo.ts': 'type A = { n?: number }; export type B = Required'
+ '/foo.ts': 'type A = { n?: number }; export type B = Required',
}
const { props } = resolve(
`
import { B } from './foo'
defineProps()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- n: ['Number']
+ n: ['Number'],
})
})
@@ -613,7 +613,7 @@ describe('resolveType', () => {
'/foo.vue':
'',
'/bar.vue':
- ''
+ '',
}
const { props, deps } = resolve(
`
@@ -621,11 +621,11 @@ describe('resolveType', () => {
import { P as PP } from './bar.vue'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -635,18 +635,18 @@ describe('resolveType', () => {
'/foo.ts': `import type { P as PP } from './nested/bar.vue'
export type P = { foo: number } & PP`,
'/nested/bar.vue':
- ''
+ '',
}
const { props, deps } = resolve(
`
import { P } from './foo'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -654,17 +654,17 @@ describe('resolveType', () => {
test('relative (chained, re-export)', () => {
const files = {
'/foo.ts': `export { P as PP } from './bar'`,
- '/bar.ts': 'export type P = { bar: string }'
+ '/bar.ts': 'export type P = { bar: string }',
}
const { props, deps } = resolve(
`
import { PP as P } from './foo'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -672,17 +672,17 @@ describe('resolveType', () => {
test('relative (chained, export *)', () => {
const files = {
'/foo.ts': `export * from './bar'`,
- '/bar.ts': 'export type P = { bar: string }'
+ '/bar.ts': 'export type P = { bar: string }',
}
const { props, deps } = resolve(
`
import { P } from './foo'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -690,7 +690,7 @@ describe('resolveType', () => {
test('relative (default export)', () => {
const files = {
'/foo.ts': `export default interface P { foo: string }`,
- '/bar.ts': `type X = { bar: string }; export default X`
+ '/bar.ts': `type X = { bar: string }; export default X`,
}
const { props, deps } = resolve(
`
@@ -698,11 +698,11 @@ describe('resolveType', () => {
import X from './bar'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -711,7 +711,7 @@ describe('resolveType', () => {
const files = {
'/bar.ts': `export { default } from './foo'`,
'/foo.ts': `export default interface P { foo: string }; export interface PP { bar: number }`,
- '/baz.ts': `export { PP as default } from './foo'`
+ '/baz.ts': `export { PP as default } from './foo'`,
}
const { props, deps } = resolve(
`
@@ -719,11 +719,11 @@ describe('resolveType', () => {
import PP from './baz'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['String'],
- bar: ['Number']
+ bar: ['Number'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -732,17 +732,17 @@ describe('resolveType', () => {
const files = {
'/foo.ts': `export default interface P { foo: string }`,
'/bar.ts': `export default interface PP { bar: number }`,
- '/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; `
+ '/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; `,
}
const { props, deps } = resolve(
`import { X, XX } from './baz'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- foo: ['String']
+ foo: ['String'],
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
@@ -788,17 +788,17 @@ describe('resolveType', () => {
test('ts module resolve', () => {
const files = {
'/node_modules/foo/package.json': JSON.stringify({
- types: 'index.d.ts'
+ types: 'index.d.ts',
}),
'/node_modules/foo/index.d.ts': 'export type P = { foo: number }',
'/tsconfig.json': JSON.stringify({
compilerOptions: {
paths: {
- bar: ['./pp.ts']
- }
- }
+ bar: ['./pp.ts'],
+ },
+ },
}),
- '/pp.ts': 'export type PP = { bar: string }'
+ '/pp.ts': 'export type PP = { bar: string }',
}
const { props, deps } = resolve(
@@ -807,16 +807,16 @@ describe('resolveType', () => {
import { PP } from 'bar'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
foo: ['Number'],
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual([
'/node_modules/foo/index.d.ts',
- '/pp.ts'
+ '/pp.ts',
])
})
@@ -825,23 +825,23 @@ describe('resolveType', () => {
'/tsconfig.json': JSON.stringify({
references: [
{
- path: './tsconfig.app.json'
- }
- ]
+ path: './tsconfig.app.json',
+ },
+ ],
}),
'/tsconfig.app.json': JSON.stringify({
include: ['**/*.ts', '**/*.vue'],
- extends: './tsconfig.web.json'
+ extends: './tsconfig.web.json',
}),
'/tsconfig.web.json': JSON.stringify({
compilerOptions: {
composite: true,
paths: {
- bar: ['./user.ts']
- }
- }
+ bar: ['./user.ts'],
+ },
+ },
}),
- '/user.ts': 'export type User = { bar: string }'
+ '/user.ts': 'export type User = { bar: string }',
}
const { props, deps } = resolve(
@@ -849,11 +849,11 @@ describe('resolveType', () => {
import { User } from 'bar'
defineProps ()
`,
- files
+ files,
)
expect(props).toStrictEqual({
- bar: ['String']
+ bar: ['String'],
})
expect(deps && [...deps]).toStrictEqual(['/src/Foo.vue'])
})
@@ -898,16 +898,16 @@ describe('resolveType', () => {
type PP = { bar: string }
}
export {}
- `
+ `,
}
const { props, deps } = resolve(`defineProps The next line is empty. This is the last line. The next line is empty. This is the last line. {{ render }} {{ foobar }} {{ foobar }} {{ foobar }} {{ foobar }} {{ foobar }} {{ foobar }}The next line contains four spaces.
The next line contains four spaces.
`,
- ssr: true
+ ssr: true,
})
expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`)
expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`)
@@ -213,7 +384,7 @@ test('should not hoist srcset URLs in SSR mode', () => {
`,
- ssr: true
+ ssr: true,
})
expect(code).toMatchSnapshot()
})
@@ -226,3 +397,65 @@ test('dynamic v-on + static v-on should merged', () => {
expect(result.code).toMatchSnapshot()
})
+
+// #9853 regression found in Nuxt tests
+// walkIdentifiers can get called multiple times on the same node
+// due to #9729 calling it during SFC template usage check.
+// conditions needed:
+// 1. `
+
+ {{ list.map((t, index) => ({ t: t })) }}
+
+ `
+ const { descriptor } = parse(src)
+ // compileScript triggers importUsageCheck
+ compileScript(descriptor, { id: 'xxx' })
+ const { code } = compileTemplate({
+ id: 'xxx',
+ filename: 'test.vue',
+ ast: descriptor.template!.ast,
+ source: descriptor.template!.content,
+ })
+ expect(code).not.toMatch(`_ctx.t`)
+})
+
+interface Pos {
+ line: number
+ column: number
+ name?: string
+}
+
+function getPositionInCode(
+ code: string,
+ token: string,
+ expectName: string | boolean = false,
+): Pos {
+ const generatedOffset = code.indexOf(token)
+ let line = 1
+ let lastNewLinePos = -1
+ for (let i = 0; i < generatedOffset; i++) {
+ if (code.charCodeAt(i) === 10 /* newline char code */) {
+ line++
+ lastNewLinePos = i
+ }
+ }
+ const res: Pos = {
+ line,
+ column:
+ lastNewLinePos === -1
+ ? generatedOffset
+ : generatedOffset - lastNewLinePos - 1,
+ }
+ if (expectName) {
+ res.name = typeof expectName === 'string' ? expectName : token
+ }
+ return res
+}
diff --git a/packages/compiler-sfc/__tests__/cssVars.spec.ts b/packages/compiler-sfc/__tests__/cssVars.spec.ts
index d57ad079d..323c9c7a5 100644
--- a/packages/compiler-sfc/__tests__/cssVars.spec.ts
+++ b/packages/compiler-sfc/__tests__/cssVars.spec.ts
@@ -1,5 +1,5 @@
import { compileStyle, parse } from '../src'
-import { mockId, compileSFCScript, assertCode } from './utils'
+import { assertCode, compileSFCScript, mockId } from './utils'
describe('CSS vars injection', () => {
test('generating correct code for nested paths', () => {
@@ -8,7 +8,7 @@ describe('CSS vars injection', () => {
``
+ }`,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-color": (_ctx.color),
@@ -32,7 +32,7 @@ describe('CSS vars injection', () => {
div {
font-size: v-bind(size);
}
- `
+ `,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-size": (_ctx.size)
@@ -57,7 +57,7 @@ describe('CSS vars injection', () => {
font-size: v-bind(size);
border: v-bind(foo);
}
- `
+ `,
)
// should handle:
// 1. local const bindings
@@ -69,7 +69,7 @@ describe('CSS vars injection', () => {
"${mockId}-foo": (__props.foo)
})`)
expect(content).toMatch(
- `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
+ `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
)
assertCode(content)
})
@@ -85,7 +85,7 @@ describe('CSS vars injection', () => {
font-family: v-bind(フォント);
}`,
filename: 'test.css',
- id: 'data-v-test'
+ id: 'data-v-test',
})
expect(code).toMatchInlineSnapshot(`
".foo {
@@ -106,7 +106,7 @@ describe('CSS vars injection', () => {
color: v-bind(color);
font-size: v-bind('font.size');
}`,
- { isProd: true }
+ { isProd: true },
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"4003f1a6": (_ctx.color),
@@ -120,7 +120,7 @@ describe('CSS vars injection', () => {
}`,
filename: 'test.css',
id: mockId,
- isProd: true
+ isProd: true,
})
expect(code).toMatchInlineSnapshot(`
".foo {
@@ -135,8 +135,8 @@ describe('CSS vars injection', () => {
assertCode(
compileSFCScript(
`\n` +
- ``
- ).content
+ ``,
+ ).content,
)
})
@@ -144,8 +144,8 @@ describe('CSS vars injection', () => {
assertCode(
compileSFCScript(
`\n` +
- ``
- ).content
+ ``,
+ ).content,
)
})
@@ -155,8 +155,8 @@ describe('CSS vars injection', () => {
`\n` + ``
- ).content
+ \n` + ``,
+ ).content,
)
})
@@ -164,8 +164,8 @@ describe('CSS vars injection', () => {
assertCode(
compileSFCScript(
`\n` +
- ``
- ).content
+ ``,
+ ).content,
)
})
@@ -178,7 +178,7 @@ describe('CSS vars injection', () => {
div{ /* color: v-bind(color); */ width:20; }
div{ width: v-bind(width); }
/* comment */
- `
+ `,
)
expect(content).not.toMatch(`"${mockId}-color": (color)`)
@@ -198,7 +198,7 @@ describe('CSS vars injection', () => {
p {
color: v-bind(color);
}
- `
+ `,
)
// color should only be injected once, even if it is twice in style
expect(content).toMatch(`_useCssVars(_ctx => ({
@@ -229,7 +229,7 @@ describe('CSS vars injection', () => {
p {
color: v-bind(((a + b)) / (2 * a));
}
- `
+ `,
)
expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-foo": (_unref(foo)),
@@ -243,7 +243,7 @@ describe('CSS vars injection', () => {
// #6022
test('should be able to parse incomplete expressions', () => {
const {
- descriptor: { cssVars }
+ descriptor: { cssVars },
} = parse(
`
`
+ `,
)
expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
})
@@ -266,10 +266,10 @@ describe('CSS vars injection', () => {
label {
background: v-bind(background);
}
- `
+ `,
)
expect(content).toMatch(
- `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`
+ `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`,
)
})
@@ -287,9 +287,9 @@ describe('CSS vars injection', () => {
{
inlineTemplate: true,
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).not.toMatch(`_useCssVars`)
})
@@ -308,9 +308,9 @@ describe('CSS vars injection', () => {
{
inlineTemplate: false,
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).not.toMatch(`_useCssVars`)
})
@@ -333,9 +333,9 @@ describe('CSS vars injection', () => {
`,
{
templateOptions: {
- ssr: true
- }
- }
+ ssr: true,
+ },
+ },
)
expect(content).not.toMatch(`_useCssVars`)
})
diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts
index ae362da02..048dab693 100644
--- a/packages/compiler-sfc/__tests__/parse.spec.ts
+++ b/packages/compiler-sfc/__tests__/parse.spec.ts
@@ -1,5 +1,10 @@
import { parse } from '../src'
-import { baseParse, baseCompile } from '@vue/compiler-core'
+import {
+ ElementTypes,
+ NodeTypes,
+ baseCompile,
+ createRoot,
+} from '@vue/compiler-core'
import { SourceMapConsumer } from 'source-map-js'
describe('compiler:sfc', () => {
@@ -7,15 +12,61 @@ describe('compiler:sfc', () => {
test('style block', () => {
// Padding determines how many blank lines will there be before the style block
const padding = Math.round(Math.random() * 10)
- const style = parse(
- `${'\n'.repeat(padding)}\n`
- ).descriptor.styles[0]
+ const src =
+ `${'\n'.repeat(padding)}` +
+ `
- expect(style.map).not.toBeUndefined()
+
- const consumer = new SourceMapConsumer(style.map!)
+
+
+`
+ const {
+ descriptor: { styles },
+ } = parse(src)
+
+ expect(styles[0].map).not.toBeUndefined()
+ const consumer = new SourceMapConsumer(styles[0].map!)
+ const lineOffset =
+ src.slice(0, src.indexOf(`\n