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

feat(vscode): support annotation for pug #1881

Merged
merged 8 commits into from Nov 29, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
69 changes: 64 additions & 5 deletions packages/shared-common/src/index.ts
Expand Up @@ -28,7 +28,58 @@ export function replaceAsync(string: string, searchValue: RegExp, replacer: (...
}
}

export function getMatchedPositions(code: string, matched: string[], hasVariantGroup = false) {
export async function isPug(uno: UnoGenerator, code: string, id = '') {
const pugExtractor = uno.config.extractors?.find(e => e.name === 'pug')
if (!pugExtractor)
return { pug: false, code: '' }

const ctx = { code, id } as any
await pugExtractor.extract(ctx)
return ctx.code !== code ? { pug: true, code: ctx.code as string } : { pug: false, code: '' }
}

export function getPlainClassMatchedPositionsForPug(codeSplit: string, matchedPlain: Set<string>, start: number) {
const result: [number, number, string][] = []
matchedPlain.forEach((plainClassName) => {
// normal case: match for 'p1'
// end with EOL : div.p1
// end with . : div.p1.ma
// end with # : div.p1#id
// end with = : div.p1= content
// end with space : div.p1 content
// end with ( : div.p1(text="red")

// complex case: match for hover:scale-100
// such as [div.hover:scale-100] will not be parsed correctly by pug
// should use [div(class='hover:scale-100')]

// combine both cases will be 2 syntax
// div.p1(class='hover:scale-100')
// div(class='hover:scale-100 p1') -> p1 should be parsing as well
if (plainClassName.includes(':')) {
if (plainClassName === codeSplit)
result.push([start, start + plainClassName.length, plainClassName])
}
else {
const regex = new RegExp(`\.(${plainClassName})[\.#=\s(]|\.(${plainClassName})$`)
const match = regex.exec(codeSplit)
if (match) {
// keep [.] not include -> .p1 will only show underline on [p1]
result.push([start + match.index + 1, start + match.index + plainClassName.length + 1, plainClassName])
}
else {
// div(class='hover:scale-100 p1') -> parsing p1
// this will only be triggered if normal case fails
if (plainClassName === codeSplit)
result.push([start, start + plainClassName.length, plainClassName])
}
}
})

return result
}

export function getMatchedPositions(code: string, matched: string[], hasVariantGroup = false, isPug = false) {
const result: [number, number, string][] = []
const attributify: RegExpMatchArray[] = []
const plain = new Set<string>()
Expand All @@ -48,8 +99,13 @@ export function getMatchedPositions(code: string, matched: string[], hasVariantG
let start = 0
code.split(/([\s"'`;<>]|:\(|\)"|\)\s)/g).forEach((i) => {
const end = start + i.length
if (plain.has(i))
result.push([start, end, i])
if (isPug) {
result.push(...getPlainClassMatchedPositionsForPug(i, plain, start))
}
else {
if (plain.has(i))
result.push([start, end, i])
}
start = end
})

Expand Down Expand Up @@ -114,6 +170,9 @@ export async function getMatchedPositionsFromCode(uno: UnoGenerator, code: strin
for (const i of transformers?.filter(i => i.enforce === 'post') || [])
await i.transform(s, id, ctx)
const hasVariantGroup = !!uno.config.transformers?.find(i => i.name === 'variant-group')
const result = await uno.generate(s.toString(), { preflights: false })
return getMatchedPositions(code, [...result.matched], hasVariantGroup)

const { pug, code: pugCode } = await isPug(uno, s.toString(), id)
azaleta marked this conversation as resolved.
Show resolved Hide resolved
const result = await uno.generate(pug ? pugCode : s.toString(), { preflights: false })
return getMatchedPositions(code, [...result.matched], hasVariantGroup, pug)
}

101 changes: 101 additions & 0 deletions test/__snapshots__/pos.test.ts.snap
@@ -0,0 +1,101 @@
// Vitest Snapshot v1

exports[`matched-positions-pug > plain class: normal case 1`] = `
[
[
28,
30,
"p1",
],
[
31,
33,
"ma",
],
[
44,
46,
"p2",
],
[
61,
63,
"p4",
],
[
64,
66,
"p5",
],
[
68,
70,
"p6",
],
[
81,
83,
"p7",
],
[
84,
86,
"p8",
],
[
97,
99,
"p9",
],
[
106,
109,
"[text=\\"red\\"]",
],
]
`;

exports[`matched-positions-pug > plain class: prefix 1`] = `
[
[
35,
50,
"hover:scale-100",
],
[
68,
82,
"hover:scale-90",
],
[
100,
114,
"hover:scale-80",
],
[
115,
117,
"p1",
],
[
135,
149,
"hover:scale-70",
],
[
150,
152,
"p2",
],
[
164,
166,
"p3",
],
[
174,
188,
"hover:scale-60",
],
]
`;
131 changes: 130 additions & 1 deletion test/pos.test.ts
@@ -1,11 +1,14 @@
import { describe, expect, test } from 'vitest'
import presetAttributify from '@unocss/preset-attributify'
import presetUno from '@unocss/preset-uno'
import { createGenerator } from '@unocss/core'
import type { UnoGenerator } from '@unocss/core'
import { createGenerator, extractorSplit } from '@unocss/core'
import { getMatchedPositionsFromCode as match } from '@unocss/shared-common'
import transformerVariantGroup from '@unocss/transformer-variant-group'
import cssDirectives from '@unocss/transformer-directives'

import extractorPug from '@unocss/extractor-pug'

describe('matched-positions', async () => {
test('attributify', async () => {
const uno = createGenerator({
Expand Down Expand Up @@ -139,3 +142,129 @@ describe('matched-positions', async () => {
})
})

describe('matched-positions-pug', async () => {
const matchPug = (uno: UnoGenerator, code: string) => {
return match(uno,
`<template lang='pug'>
${code}
</template>`, 'App.vue')
}

const uno = createGenerator({
presets: [
presetUno(),
presetAttributify({ strict: true }),
],
extractors: [
extractorSplit,
extractorPug(),
],
transformers: [
transformerVariantGroup(),
],
})

test('plain class: normal case', async () => {
const pugCode = `div.p1.ma
div.p2#id1
div.p4.p5= p6
div.p7 p8
div.p9(text="red")
`
expect(await matchPug(uno, pugCode)).toMatchSnapshot()
}, 20000)

test('plain class: prefix', async () => {
const pugCode = `div(class='hover:scale-100')
div(class="hover:scale-90")
div(class="hover:scale-80 p1")
div(class="hover:scale-70 p2 ")
div.p3(class="hover:scale-60")
`
expect(await matchPug(uno, pugCode)).toMatchSnapshot()
}, 20000)

test('attributify', async () => {
const pugCode = `div.p4(border="b gray4")
div(text='red')
`
expect(await matchPug(uno, pugCode)).toMatchInlineSnapshot(`
[
[
28,
30,
"p4",
],
[
39,
40,
"b",
],
[
39,
40,
"[border=\\"b\\"]",
],
[
41,
46,
"[border=\\"gray4\\"]",
],
[
65,
68,
"[text=\\"red\\"]",
],
]
`)
})

test('variant group', async () => {
const pugCode = 'div.p4(class="hover:(h-4 w-4)")'
expect(await matchPug(uno, pugCode)).toMatchInlineSnapshot(`
[
[
28,
30,
"p4",
],
[
45,
48,
"hover:h-4",
],
[
49,
52,
"hover:w-4",
],
]
`)
})

test('css-directive', async () => {
// \n could not be include
// div.p2(class="btn-center{@apply p1 m1;\n}") -> pug parse error
const pugCode = 'div.p2(class="btn-center{@apply p1 m1;}")'
expect(await matchPug(uno, pugCode)).toMatchInlineSnapshot(`
[
[
28,
30,
"p2",
],
[
56,
58,
"p1",
],
[
59,
61,
"m1",
],
]
`)
})
})