From cc3195945f74df563f1a7f972e5d4cef091ecb04 Mon Sep 17 00:00:00 2001 From: baiwusanyu-c <740132583@qq.com> Date: Thu, 23 Mar 2023 17:06:17 +0800 Subject: [PATCH 01/10] chore: temp commit --- packages/core/css/pre-process-css.ts | 16 ++++++++++------ packages/core/parser/parser-import.ts | 5 +++-- play/src/App.vue | 6 +++--- play/src/assets/sass/foo.sass | 4 ++++ play/src/assets/sass/test.sass | 2 ++ play/src/assets/scss/bar.scss | 2 +- play/src/assets/styuls/test.styl | 3 +-- 7 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 play/src/assets/sass/foo.sass create mode 100644 play/src/assets/sass/test.sass diff --git a/packages/core/css/pre-process-css.ts b/packages/core/css/pre-process-css.ts index 21ca23c..6bc3d39 100644 --- a/packages/core/css/pre-process-css.ts +++ b/packages/core/css/pre-process-css.ts @@ -205,18 +205,23 @@ export function generateCSSCode(path: string, suffix: string) { // @import 编译 scss,会一直编译,一直到遇到 import 了一个 css 或没有 import 为止 // 这里先分析出 imports,在根据其内容将 sass 中 import 删除 // 编译 sass 为 css,再复原 + // eslint-disable-next-line no-case-declarations + const parseScssImporter = parseImports(code) + // eslint-disable-next-line no-case-declarations + const codeScssNoImporter = getCurFileContent(code, parseScssImporter.imports) + // eslint-disable-next-line no-case-declarations + const scssParseRes = sass.compileString(codeScssNoImporter) + res = setImportToCompileRes(scssParseRes.css, parseScssImporter.imports) + break + case `.${SUPPORT_FILE.SASS}`: // sass // eslint-disable-next-line no-case-declarations const parseSassImporter = parseImports(code) // eslint-disable-next-line no-case-declarations const codeNoImporter = getCurFileContent(code, parseSassImporter.imports) // eslint-disable-next-line no-case-declarations - const sassParseRes = sass.compileString(codeNoImporter) + const sassParseRes = sass.compileString(codeNoImporter, { syntax: 'indented' }) res = setImportToCompileRes(sassParseRes.css, parseSassImporter.imports) break - case `.${SUPPORT_FILE.SASS}`: // sass - // ⭐TODO: 支持 sass - res = '' - break case `.${SUPPORT_FILE.LESS}`: // less // eslint-disable-next-line no-case-declarations const parseLessImporter = parseImports(code) @@ -230,7 +235,6 @@ export function generateCSSCode(path: string, suffix: string) { }) break case `.${SUPPORT_FILE.STYL}`: // stylus - // TODO unit test // eslint-disable-next-line no-case-declarations const parseStylusImporter = parseImports(code) // eslint-disable-next-line no-case-declarations diff --git a/packages/core/parser/parser-import.ts b/packages/core/parser/parser-import.ts index e182e1e..4b4007d 100644 --- a/packages/core/parser/parser-import.ts +++ b/packages/core/parser/parser-import.ts @@ -6,7 +6,8 @@ export enum ParserState { AtRequire, StringLiteral, } - +// TODO: 解析, 语法(sass) +// TODO: 无引号和分号;语法(sass) export interface ImportStatement { type: 'import' | 'use' | 'require' path: string @@ -56,7 +57,7 @@ export function parseImports(source: string): { state = ParserState.StringLiteral currentImport!.start = i currentImport!.path += char - } else if (char === ';') { + } else if (char === ';' || char === '\n') { if (currentImport && currentImport.start !== undefined) { currentImport.end = i imports.push(currentImport) diff --git a/play/src/App.vue b/play/src/App.vue index 3da8ab2..9556d3a 100644 --- a/play/src/App.vue +++ b/play/src/App.vue @@ -63,13 +63,13 @@ export default { --> - diff --git a/play/src/assets/sass/foo.sass b/play/src/assets/sass/foo.sass new file mode 100644 index 0000000..721b547 --- /dev/null +++ b/play/src/assets/sass/foo.sass @@ -0,0 +1,4 @@ +@import test.sass +#app + div + color: v-bind(stylColor) diff --git a/play/src/assets/sass/test.sass b/play/src/assets/sass/test.sass new file mode 100644 index 0000000..e804da3 --- /dev/null +++ b/play/src/assets/sass/test.sass @@ -0,0 +1,2 @@ +.sass + color: v-bind(color) diff --git a/play/src/assets/scss/bar.scss b/play/src/assets/scss/bar.scss index 6543921..448776a 100644 --- a/play/src/assets/scss/bar.scss +++ b/play/src/assets/scss/bar.scss @@ -1 +1 @@ -@import "./foo.scss.scss"; +@import "./foo.scss"; diff --git a/play/src/assets/styuls/test.styl b/play/src/assets/styuls/test.styl index eaf6608..06272a4 100644 --- a/play/src/assets/styuls/test.styl +++ b/play/src/assets/styuls/test.styl @@ -1,3 +1,2 @@ -.styl { +.styl color: v-bind(color); -} From 6987d9403a6d5de912f3dd9d70e410f28fade999 Mon Sep 17 00:00:00 2001 From: baiwusanyu-c <740132583@qq.com> Date: Thu, 23 Mar 2023 18:27:13 +0800 Subject: [PATCH 02/10] =?UTF-8?q?chore:=20=E4=B8=B4=E6=97=B6=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parser/__test__/parser-import.spec.ts | 74 +++++++++++ packages/core/parser/parser-import-next.ts | 115 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 packages/core/parser/parser-import-next.ts diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 6500350..6f7d28d 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'vitest' import { ParserState, parseImports } from '../parser-import' +import { parseImportsNext } from '../parser-import-next' describe('parse import', () => { test('parseImports: Initial -> At', () => { @@ -150,4 +151,77 @@ describe('parse import', () => { { type: 'require', path: '\'./test-require\'', start: 46, end: 62 }, ]) }) + + test('parseImports: TESSSSSST', () => { + //const test1 = ' @import \'./test.css\';\n' // { type: 'import', path: '\'./test.css\''} + //expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: '\'./test.css\'' }]) + + //const test2 = '@import \'test\';\n' // { type: 'import', path: '\'./test\''} + //expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) + + const test3 = '@import \\"test.css\\";\n' // { type: 'import', path: '\\"./test.css\\"'} + expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: '\\"test.css\\"' }]) + + //const test4 = '@import \\"test\\";\n' // { type: 'import', path: '\\"./test\\"'} + //expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: '\\"test\\"' }]) + + const test5 = '@import \'test.css\'' // { type: 'import', path: '\'./test.css\''} + const test6 = '@import \'test\'' // { type: 'import', path: '\'./test\''} + const test7 = '@import \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} + const test8 = '@import \\"test\\"' // { type: 'import', path: '\\"./test\\"'} + + const test9 = '@importtest\\"' // 不解析 + + const test10 = '@use \'test.css\';' // { type: 'import', path: '\'./test.css\''} + const test11 = '@use \'test\';' // { type: 'import', path: '\'./test\''} + const test12 = '@use \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} + const test13 = '@use \\"test\\";' // { type: 'import', path: '\\"./test\\"'} + + const test14 = '@use \'test.css\'' // { type: 'import', path: '\'./test.css\''} + const test15 = '@use \'test\'' // { type: 'import', path: '\'./test\''} + const test16 = '@use \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} + const test17 = '@use \\"test\\"' // { type: 'import', path: '\\"./test\\"'} + + const test18 = '@usetest\\"' // 不解析 + + const test19 = '@require \'test.css\';' // { type: 'import', path: '\'./test.css\''} + const test20 = '@require \'test\';' // { type: 'import', path: '\'./test\''} + const test21 = '@require \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} + const test22 = '@require \\"test\\";' // { type: 'import', path: '\\"./test\\"'} + + const test23 = '@require \'test.css\'' // { type: 'import', path: '\'./test.css\''} + const test24 = '@require \'test\'' // { type: 'import', path: '\'./test\''} + const test25 = '@require \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} + const test26 = '@require \\"test\\"' // { type: 'import', path: '\\"./test\\"'} + + const test27 = '@requiretest\\"' // 不解析 + + const test28 = '@import ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test29 = '@import ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + + const test30 = '@use ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test31 = '@use ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + + const test32 = '@require ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test33 = '@require ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + + const test34 = '@import \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + const test35 = '@import \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + + const test36 = '@use \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + const test37 = '@use \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + + const test38 = '@require \\"./test1\\", \\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + const test39 = '@require \\"./test1\\", \\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + + const test40 = '@import \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test41 = '@import \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + + const test42 = '@use \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test43 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + + const test44 = '@require \'./test1\', \'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test45 = '@require \'./test1\', \'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + // TODO:注释 + }) }) diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts new file mode 100644 index 0000000..924ff1b --- /dev/null +++ b/packages/core/parser/parser-import-next.ts @@ -0,0 +1,115 @@ +export enum ParserState { + Initial, + At, + AtImport, + AtUse, + AtRequire, + StringLiteral, +} + +export interface ImportStatement { + type: 'import' | 'use' | 'require' + path: string + start?: number + end?: number + suffix?: string +} +// const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\'"]/g, '') +export function parseImportsNext(content: string): { + imports: ImportStatement[] + getCurState: () => ParserState + getCurImport: () => undefined | ImportStatement +} { + const imports: ImportStatement[] = [] + let currentImport: ImportStatement | undefined + let state = ParserState.Initial + let i = 0 + let AtPath = '' + let source = content + while (i < source.length) { + const char = source[i] + switch (state) { + case ParserState.Initial: + if (char === '@') + state = ParserState.At + break + case ParserState.At: + AtPath = AtPath + char + if (AtPath === 'import') { + AtPath = '' + state = ParserState.AtImport + currentImport = { type: 'import', path: '' } + i++ // skip over "i" to next character + } else if (AtPath === 'use') { + AtPath = '' + state = ParserState.AtUse + currentImport = { type: 'use', path: '' } + i++ // skip over "u" to next character + } else if (AtPath === 'require') { + AtPath = '' + state = ParserState.AtRequire + currentImport = { type: 'require', path: '' } + i++ // skip over "u" to next character + } + // TODO ... + if (AtPath.length >= 7 && AtPath !== 'require') + state = ParserState.Initial + + break + case ParserState.AtImport: + case ParserState.AtUse: + case ParserState.AtRequire: + debugger + // 当字符不是空格,且前一个是空格,进入取值 + if ((char !== ' ' && source[i - 1] === ' ') + || (!/[\n\r\t\f\v\\'"]/g.test(char) + && /[\n\r\t\f\v\\'"]/g.test(source[i - 1]))) { + state = ParserState.StringLiteral + currentImport!.start = i + currentImport!.path += char + } else if (char === '\n' || i === source.length - 1) { + if (currentImport && currentImport.start !== undefined) { + currentImport.end = i + imports.push(currentImport) + currentImport = undefined + } + state = ParserState.Initial + } + break + case ParserState.StringLiteral: + // 遇到引号,其起始位置也是,则是有引号状态下的取值结束 + if ((char === "'" || char === '"') + && (currentImport!.start || currentImport!.start === 0) + && (source[currentImport!.start] === char)) { + if (currentImport!.type === 'import') + state = ParserState.AtImport + + if (currentImport!.type === 'use') + state = ParserState.AtUse + + if (currentImport!.type === 'require') + state = ParserState.AtRequire + + currentImport!.path += char + } else { + // 取值 + currentImport!.path += char + } + + break + } + i++ + } + + function getCurState() { + return state + } + function getCurImport() { + return currentImport + } + return { + imports, + getCurState, + getCurImport, + } +} From 182fffb7a9f6b579227b7ed818866a00260dc9f1 Mon Sep 17 00:00:00 2001 From: baiwusanyu-c <740132583@qq.com> Date: Thu, 23 Mar 2023 18:32:14 +0800 Subject: [PATCH 03/10] =?UTF-8?q?chore:=20=E4=B8=B4=E6=97=B6=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/parser/__test__/parser-import.spec.ts | 16 ++++++++-------- packages/core/parser/parser-import-next.ts | 8 +++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 6f7d28d..2f82ecc 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -153,17 +153,17 @@ describe('parse import', () => { }) test('parseImports: TESSSSSST', () => { - //const test1 = ' @import \'./test.css\';\n' // { type: 'import', path: '\'./test.css\''} - //expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: '\'./test.css\'' }]) + const test1 = ' @import \'./test.css\';' // { type: 'import', path: '\'./test.css\''} + expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: '\'./test.css\'' }]) - //const test2 = '@import \'test\';\n' // { type: 'import', path: '\'./test\''} - //expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) + const test2 = '@import \'test\';\n' // { type: 'import', path: '\'./test\''} + expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) - const test3 = '@import \\"test.css\\";\n' // { type: 'import', path: '\\"./test.css\\"'} - expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: '\\"test.css\\"' }]) + const test3 = '@import \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} + expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: '"test.css"' }]) - //const test4 = '@import \\"test\\";\n' // { type: 'import', path: '\\"./test\\"'} - //expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: '\\"test\\"' }]) + const test4 = '@import \\"test\\";\n' // { type: 'import', path: '\\"./test\\"'} + expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: '"test"' }]) const test5 = '@import \'test.css\'' // { type: 'import', path: '\'./test.css\''} const test6 = '@import \'test\'' // { type: 'import', path: '\'./test\''} diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts index 924ff1b..f53703f 100644 --- a/packages/core/parser/parser-import-next.ts +++ b/packages/core/parser/parser-import-next.ts @@ -14,7 +14,7 @@ export interface ImportStatement { end?: number suffix?: string } -// const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\'"]/g, '') +const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\]/g, '') export function parseImportsNext(content: string): { imports: ImportStatement[] getCurState: () => ParserState @@ -25,7 +25,7 @@ export function parseImportsNext(content: string): { let state = ParserState.Initial let i = 0 let AtPath = '' - let source = content + const source = delTransformSymbol(content) while (i < source.length) { const char = source[i] switch (state) { @@ -61,9 +61,7 @@ export function parseImportsNext(content: string): { case ParserState.AtRequire: debugger // 当字符不是空格,且前一个是空格,进入取值 - if ((char !== ' ' && source[i - 1] === ' ') - || (!/[\n\r\t\f\v\\'"]/g.test(char) - && /[\n\r\t\f\v\\'"]/g.test(source[i - 1]))) { + if (char !== ' ' && source[i - 1] === ' ') { state = ParserState.StringLiteral currentImport!.start = i currentImport!.path += char From a8c5de065fb9940153abd9ada9f03b6507bde9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=9B=BE=E4=B8=89=E8=AF=AD?= <32354856+baiwusanyu-c@users.noreply.github.com> Date: Thu, 23 Mar 2023 21:43:31 +0800 Subject: [PATCH 04/10] chore: temp commit --- .../parser/__test__/parser-import.spec.ts | 94 ++++++++++++++----- packages/core/parser/parser-import-next.ts | 38 +++++--- 2 files changed, 98 insertions(+), 34 deletions(-) diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 2f82ecc..19106a5 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -153,51 +153,101 @@ describe('parse import', () => { }) test('parseImports: TESSSSSST', () => { - const test1 = ' @import \'./test.css\';' // { type: 'import', path: '\'./test.css\''} + const test1 = ' @import \'./test.css\';' expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: '\'./test.css\'' }]) - const test2 = '@import \'test\';\n' // { type: 'import', path: '\'./test\''} + const test2 = '@import \'test\';\n' expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) - const test3 = '@import \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} + const test3 = '@import \\"test.css\\";' expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: '"test.css"' }]) - const test4 = '@import \\"test\\";\n' // { type: 'import', path: '\\"./test\\"'} + const test4 = '@import \\"test\\";\n' expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: '"test"' }]) - const test5 = '@import \'test.css\'' // { type: 'import', path: '\'./test.css\''} - const test6 = '@import \'test\'' // { type: 'import', path: '\'./test\''} - const test7 = '@import \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} - const test8 = '@import \\"test\\"' // { type: 'import', path: '\\"./test\\"'} + const test5 = '@import \'test.css\'\n' + expect(parseImportsNext(test5).imports).toMatchObject([{ type: 'import', path: '\'test.css\'' }]) - const test9 = '@importtest\\"' // 不解析 + const test6 = '@import \'test\'\n' + expect(parseImportsNext(test6).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) - const test10 = '@use \'test.css\';' // { type: 'import', path: '\'./test.css\''} - const test11 = '@use \'test\';' // { type: 'import', path: '\'./test\''} - const test12 = '@use \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} - const test13 = '@use \\"test\\";' // { type: 'import', path: '\\"./test\\"'} + const test7 = '@import \\"test.css\\"' + expect(parseImportsNext(test7).imports).toMatchObject([{ type: 'import', path: '"test.css"' }]) - const test14 = '@use \'test.css\'' // { type: 'import', path: '\'./test.css\''} - const test15 = '@use \'test\'' // { type: 'import', path: '\'./test\''} - const test16 = '@use \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} - const test17 = '@use \\"test\\"' // { type: 'import', path: '\\"./test\\"'} + const test8 = '@import \\"test\\"' + expect(parseImportsNext(test8).imports).toMatchObject([{ type: 'import', path: '"test"' }]) - const test18 = '@usetest\\"' // 不解析 + const test9 = '@importB' // 不解析 + expect(parseImportsNext(test9).imports).toMatchObject([]) + + const test10 = '@use \'test.css\';' + expect(parseImportsNext(test10).imports).toMatchObject([{ type: 'use', path: '\'test.css\'' }]) + + const test11 = '@use \'test\';' + expect(parseImportsNext(test11).imports).toMatchObject([{ type: 'use', path: '\'test\'' }]) + + const test12 = '@use \\"./test.css\\";' + expect(parseImportsNext(test12).imports).toMatchObject([{ type: 'use', path: '"./test.css"' }]) + + const test13 = '@use \\"./test\\";\n' + expect(parseImportsNext(test13).imports).toMatchObject([{ type: 'use', path: '"./test"' }]) + + const test14 = '@use \'test.css\'' + expect(parseImportsNext(test14).imports).toMatchObject([{ type: 'use', path: '\'test.css\'' }]) + + const test15 = '@use \'test\'' + expect(parseImportsNext(test15).imports).toMatchObject([{ type: 'use', path: '\'test\'' }]) + + const test16 = '@use \\"./test.css\\"' + expect(parseImportsNext(test16).imports).toMatchObject([{ type: 'use', path: '"./test.css"' }]) + + const test17 = '@use \\"test\\"' + expect(parseImportsNext(test17).imports).toMatchObject([{ type: 'use', path: '"test"' }]) + + const test18 = '@usetest' // 不解析 + expect(parseImportsNext(test18).imports).toMatchObject([]) const test19 = '@require \'test.css\';' // { type: 'import', path: '\'./test.css\''} + expect(parseImportsNext(test19).imports).toMatchObject([{ type: 'require', path: '\'test.css\'' }]) + const test20 = '@require \'test\';' // { type: 'import', path: '\'./test\''} + expect(parseImportsNext(test20).imports).toMatchObject([{ type: 'require', path: '\'test\'' }]) + const test21 = '@require \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} + expect(parseImportsNext(test21).imports).toMatchObject([{ type: 'require', path: '"test.css"' }]) + const test22 = '@require \\"test\\";' // { type: 'import', path: '\\"./test\\"'} + expect(parseImportsNext(test22).imports).toMatchObject([{ type: 'require', path: '"test"' }]) const test23 = '@require \'test.css\'' // { type: 'import', path: '\'./test.css\''} + expect(parseImportsNext(test23).imports).toMatchObject([{ type: 'require', path: '\'test.css\'' }]) + const test24 = '@require \'test\'' // { type: 'import', path: '\'./test\''} + expect(parseImportsNext(test24).imports).toMatchObject([{ type: 'require', path: '\'test\'' }]) + const test25 = '@require \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} + expect(parseImportsNext(test25).imports).toMatchObject([{ type: 'require', path: '"test.css"' }]) + const test26 = '@require \\"test\\"' // { type: 'import', path: '\\"./test\\"'} + expect(parseImportsNext(test26).imports).toMatchObject([{ type: 'require', path: '"test"' }]) - const test27 = '@requiretest\\"' // 不解析 + const test27 = '@requiretest' // 不解析 + expect(parseImportsNext(test27).imports).toMatchObject([]) - const test28 = '@import ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - const test29 = '@import ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test28_1 = '@require test.css' + expect(parseImportsNext(test28_1).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) + + const test28_2 = '@require ./test' + expect(parseImportsNext(test28_2).imports).toMatchObject([{ type: 'require', path: './test' }]) + + const test28_3 = '@require test;' + expect(parseImportsNext(test28_3).imports).toMatchObject([{ type: 'require', path: 'test;' }]) + + const test28_4 = '@require ./test\n' + expect(parseImportsNext(test28_4).imports).toMatchObject([{ type: 'require', path: './test\n' }]) + + const test28 = '@import ./test1,./test2\n'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test29 = '@import ./test1,./test2;\n'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} const test30 = '@use ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} const test31 = '@use ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} @@ -222,6 +272,8 @@ describe('parse import', () => { const test44 = '@require \'./test1\', \'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} const test45 = '@require \'./test1\', \'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + + const test46 = '@requiretest\\"' // ', " 报错 // TODO:注释 }) }) diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts index f53703f..6745633 100644 --- a/packages/core/parser/parser-import-next.ts +++ b/packages/core/parser/parser-import-next.ts @@ -39,43 +39,39 @@ export function parseImportsNext(content: string): { AtPath = '' state = ParserState.AtImport currentImport = { type: 'import', path: '' } - i++ // skip over "i" to next character } else if (AtPath === 'use') { AtPath = '' state = ParserState.AtUse currentImport = { type: 'use', path: '' } - i++ // skip over "u" to next character } else if (AtPath === 'require') { AtPath = '' state = ParserState.AtRequire currentImport = { type: 'require', path: '' } - i++ // skip over "u" to next character } - // TODO ... - if (AtPath.length >= 7 && AtPath !== 'require') + + // '@importtest' 直接回到 Initial 不再处理 + if ((state !== ParserState.At && source[i + 1] !== ' ') + || i === source.length - 1) { state = ParserState.Initial + currentImport = undefined + } break case ParserState.AtImport: case ParserState.AtUse: case ParserState.AtRequire: - debugger // 当字符不是空格,且前一个是空格,进入取值 if (char !== ' ' && source[i - 1] === ' ') { + debugger state = ParserState.StringLiteral currentImport!.start = i currentImport!.path += char } else if (char === '\n' || i === source.length - 1) { - if (currentImport && currentImport.start !== undefined) { - currentImport.end = i - imports.push(currentImport) - currentImport = undefined - } - state = ParserState.Initial + walkContentEnd(i) } break case ParserState.StringLiteral: - // 遇到引号,其起始位置也是,则是有引号状态下的取值结束 + // 遇到引号,且起始位置也是,则是有引号状态下的取值结束 if ((char === "'" || char === '"') && (currentImport!.start || currentImport!.start === 0) && (source[currentImport!.start] === char)) { @@ -94,11 +90,27 @@ export function parseImportsNext(content: string): { currentImport!.path += char } + // @require test.css 引号的情况会,会一直 + // StringLiteral 到结束 + // TODO:需要标记一下没引号情况,然后在外部处理 + // (不要再解析器里处理,这不是它的工作,对字符串任何改变都会影响 start、end) + if (i === source.length - 1) + walkContentEnd(i) + break } i++ } + function walkContentEnd(index: number) { + if (currentImport && currentImport.start !== undefined) { + currentImport.end = index + imports.push(currentImport) + currentImport = undefined + } + state = ParserState.Initial + } + function getCurState() { return state } From e123f18bfebfa0c83389083302779dd48274f73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=9B=BE=E4=B8=89=E8=AF=AD?= <32354856+baiwusanyu-c@users.noreply.github.com> Date: Thu, 23 Mar 2023 22:10:48 +0800 Subject: [PATCH 05/10] chore: temp commit --- .../parser/__test__/parser-import.spec.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 19106a5..1448251 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -153,11 +153,11 @@ describe('parse import', () => { }) test('parseImports: TESSSSSST', () => { - const test1 = ' @import \'./test.css\';' + const test1 = ' @import \'./test.css\';' expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: '\'./test.css\'' }]) - const test2 = '@import \'test\';\n' - expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) + const test2 = '@import \'test\';\n@import \'test2\';\n' + expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }, { type: 'import', path: '\'test2\'' }]) const test3 = '@import \\"test.css\\";' expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: '"test.css"' }]) @@ -245,6 +245,7 @@ describe('parse import', () => { const test28_4 = '@require ./test\n' expect(parseImportsNext(test28_4).imports).toMatchObject([{ type: 'require', path: './test\n' }]) + const test28_5 = '@require test.css@require test2.css' const test28 = '@import ./test1,./test2\n'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} const test29 = '@import ./test1,./test2;\n'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} @@ -274,6 +275,18 @@ describe('parse import', () => { const test45 = '@require \'./test1\', \'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} const test46 = '@requiretest\\"' // ', " 报错 + const test47 = 'e@requireete\\"st' // ', " 报错 + const test48 = '@requ\\"iretest' // ', " 报错 + + const test49 = '@requiretest\'' // ', " 报错 + const test50 = 'e@requireete\'st' // ', " 报错 + const test51 = '@requ\'iretest' // ', " 报错 + + const test52 = 'adwad @require test' // 忽略@前内容 + expect(parseImportsNext(test52).imports).toMatchObject([{ type: 'require', path: 'test' }]) + + const test53 = '@requiretest @require test' // 解析 + expect(parseImportsNext(test53).imports).toMatchObject([{ type: 'require', path: 'test' }]) // TODO:注释 }) }) From f0798e6a3880220c8460c0a13192a4d532d9ed98 Mon Sep 17 00:00:00 2001 From: baiwusanyu-c <740132583@qq.com> Date: Mon, 27 Mar 2023 18:01:45 +0800 Subject: [PATCH 06/10] chore: temp cimmit --- .../parser/__test__/parser-import.spec.ts | 236 +++++++++++++++--- packages/core/parser/parser-import-next.ts | 165 ++++++++---- 2 files changed, 318 insertions(+), 83 deletions(-) diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 1448251..8a1e719 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -3,7 +3,7 @@ import { ParserState, parseImports } from '../parser-import' import { parseImportsNext } from '../parser-import-next' describe('parse import', () => { - test('parseImports: Initial -> At', () => { + /* test('parseImports: Initial -> At', () => { const { getCurState } = parseImports('@') expect(getCurState()).toBe(ParserState.At) }) @@ -150,86 +150,86 @@ describe('parse import', () => { { type: 'use', path: '\'./test-use\'', start: 23, end: 35 }, { type: 'require', path: '\'./test-require\'', start: 46, end: 62 }, ]) - }) + }) */ test('parseImports: TESSSSSST', () => { - const test1 = ' @import \'./test.css\';' - expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: '\'./test.css\'' }]) + const test1 = ' @import \'./test.css\';' + expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: './test.css' }]) const test2 = '@import \'test\';\n@import \'test2\';\n' - expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: '\'test\'' }, { type: 'import', path: '\'test2\'' }]) + expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: 'test' }, { type: 'import', path: 'test2' }]) const test3 = '@import \\"test.css\\";' - expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: '"test.css"' }]) + expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) const test4 = '@import \\"test\\";\n' - expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: '"test"' }]) + expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: 'test' }]) const test5 = '@import \'test.css\'\n' - expect(parseImportsNext(test5).imports).toMatchObject([{ type: 'import', path: '\'test.css\'' }]) + expect(parseImportsNext(test5).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) const test6 = '@import \'test\'\n' - expect(parseImportsNext(test6).imports).toMatchObject([{ type: 'import', path: '\'test\'' }]) + expect(parseImportsNext(test6).imports).toMatchObject([{ type: 'import', path: 'test' }]) const test7 = '@import \\"test.css\\"' - expect(parseImportsNext(test7).imports).toMatchObject([{ type: 'import', path: '"test.css"' }]) + expect(parseImportsNext(test7).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) const test8 = '@import \\"test\\"' - expect(parseImportsNext(test8).imports).toMatchObject([{ type: 'import', path: '"test"' }]) + expect(parseImportsNext(test8).imports).toMatchObject([{ type: 'import', path: 'test' }]) const test9 = '@importB' // 不解析 expect(parseImportsNext(test9).imports).toMatchObject([]) const test10 = '@use \'test.css\';' - expect(parseImportsNext(test10).imports).toMatchObject([{ type: 'use', path: '\'test.css\'' }]) + expect(parseImportsNext(test10).imports).toMatchObject([{ type: 'use', path: 'test.css' }]) const test11 = '@use \'test\';' - expect(parseImportsNext(test11).imports).toMatchObject([{ type: 'use', path: '\'test\'' }]) + expect(parseImportsNext(test11).imports).toMatchObject([{ type: 'use', path: 'test' }]) const test12 = '@use \\"./test.css\\";' - expect(parseImportsNext(test12).imports).toMatchObject([{ type: 'use', path: '"./test.css"' }]) + expect(parseImportsNext(test12).imports).toMatchObject([{ type: 'use', path: './test.css' }]) const test13 = '@use \\"./test\\";\n' - expect(parseImportsNext(test13).imports).toMatchObject([{ type: 'use', path: '"./test"' }]) + expect(parseImportsNext(test13).imports).toMatchObject([{ type: 'use', path: './test' }]) const test14 = '@use \'test.css\'' - expect(parseImportsNext(test14).imports).toMatchObject([{ type: 'use', path: '\'test.css\'' }]) + expect(parseImportsNext(test14).imports).toMatchObject([{ type: 'use', path: 'test.css' }]) const test15 = '@use \'test\'' - expect(parseImportsNext(test15).imports).toMatchObject([{ type: 'use', path: '\'test\'' }]) + expect(parseImportsNext(test15).imports).toMatchObject([{ type: 'use', path: 'test' }]) const test16 = '@use \\"./test.css\\"' - expect(parseImportsNext(test16).imports).toMatchObject([{ type: 'use', path: '"./test.css"' }]) + expect(parseImportsNext(test16).imports).toMatchObject([{ type: 'use', path: './test.css' }]) const test17 = '@use \\"test\\"' - expect(parseImportsNext(test17).imports).toMatchObject([{ type: 'use', path: '"test"' }]) + expect(parseImportsNext(test17).imports).toMatchObject([{ type: 'use', path: 'test' }]) const test18 = '@usetest' // 不解析 expect(parseImportsNext(test18).imports).toMatchObject([]) const test19 = '@require \'test.css\';' // { type: 'import', path: '\'./test.css\''} - expect(parseImportsNext(test19).imports).toMatchObject([{ type: 'require', path: '\'test.css\'' }]) + expect(parseImportsNext(test19).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) const test20 = '@require \'test\';' // { type: 'import', path: '\'./test\''} - expect(parseImportsNext(test20).imports).toMatchObject([{ type: 'require', path: '\'test\'' }]) + expect(parseImportsNext(test20).imports).toMatchObject([{ type: 'require', path: 'test' }]) const test21 = '@require \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} - expect(parseImportsNext(test21).imports).toMatchObject([{ type: 'require', path: '"test.css"' }]) + expect(parseImportsNext(test21).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) const test22 = '@require \\"test\\";' // { type: 'import', path: '\\"./test\\"'} - expect(parseImportsNext(test22).imports).toMatchObject([{ type: 'require', path: '"test"' }]) + expect(parseImportsNext(test22).imports).toMatchObject([{ type: 'require', path: 'test' }]) const test23 = '@require \'test.css\'' // { type: 'import', path: '\'./test.css\''} - expect(parseImportsNext(test23).imports).toMatchObject([{ type: 'require', path: '\'test.css\'' }]) + expect(parseImportsNext(test23).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) const test24 = '@require \'test\'' // { type: 'import', path: '\'./test\''} - expect(parseImportsNext(test24).imports).toMatchObject([{ type: 'require', path: '\'test\'' }]) + expect(parseImportsNext(test24).imports).toMatchObject([{ type: 'require', path: 'test' }]) const test25 = '@require \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} - expect(parseImportsNext(test25).imports).toMatchObject([{ type: 'require', path: '"test.css"' }]) + expect(parseImportsNext(test25).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) const test26 = '@require \\"test\\"' // { type: 'import', path: '\\"./test\\"'} - expect(parseImportsNext(test26).imports).toMatchObject([{ type: 'require', path: '"test"' }]) + expect(parseImportsNext(test26).imports).toMatchObject([{ type: 'require', path: 'test' }]) const test27 = '@requiretest' // 不解析 expect(parseImportsNext(test27).imports).toMatchObject([]) @@ -241,52 +241,210 @@ describe('parse import', () => { expect(parseImportsNext(test28_2).imports).toMatchObject([{ type: 'require', path: './test' }]) const test28_3 = '@require test;' - expect(parseImportsNext(test28_3).imports).toMatchObject([{ type: 'require', path: 'test;' }]) + expect(parseImportsNext(test28_3).imports).toMatchObject([{ type: 'require', path: 'test' }]) const test28_4 = '@require ./test\n' - expect(parseImportsNext(test28_4).imports).toMatchObject([{ type: 'require', path: './test\n' }]) + expect(parseImportsNext(test28_4).imports).toMatchObject([{ type: 'require', path: './test' }]) + + const test28_5 = '@require test.css@require test2.css' + expect(parseImportsNext(test28_5).imports).toMatchObject([ + { type: 'require', path: 'test.css@require' }, + { type: 'require', path: 'test2.css' }, + ]) + + const test28_6 = '@require test.css,@require test2.css' + expect(parseImportsNext(test28_6).imports).toMatchObject([ + { type: 'require', path: 'test.css' }, + { type: 'require', path: 'test2.css' }, + ]) + + const test28_7 = '@require test.css;@require test2.css' + expect(parseImportsNext(test28_7).imports).toMatchObject([ + { type: 'require', path: 'test.css' }, + { type: 'require', path: 'test2.css' }, + ]) + + const test28_9 = '@require test.css; @require test2.css' + expect(parseImportsNext(test28_9).imports).toMatchObject([ + { type: 'require', path: 'test.css' }, + { type: 'require', path: 'test2.css' }, + ]) - const test28 = '@import ./test1,./test2\n'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - const test29 = '@import ./test1,./test2;\n'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test28_8 = '@require test.css, @require test2.css' + expect(parseImportsNext(test28_8).imports).toMatchObject([ + { type: 'require', path: 'test.css' }, + { type: 'require', path: 'test2.css' }, + ]) - const test30 = '@use ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - const test31 = '@use ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + const test28_10 = '@require test.css @require test2.css' + expect(parseImportsNext(test28_10).imports).toMatchObject([ + { type: 'require', path: 'test.css' }, + { type: 'require', path: 'test2.css' }, + ]) + + const test28_11 = '@require test.css @use test2.css' + expect(parseImportsNext(test28_11).imports).toMatchObject([ + { type: 'require', path: 'test.css' }, + { type: 'use', path: 'test2.css' }, + ]) + const test28 = '@import ./test1, ./test2' + expect(parseImportsNext(test28).imports).toMatchObject([ + { type: 'import', path: './test1' }, + { type: 'import', path: './test2' }, + ]) + + const test29 = '@import ./test1, ./test2;\n' + expect(parseImportsNext(test29).imports).toMatchObject([ + { type: 'import', path: './test1' }, + { type: 'import', path: './test2' }, + ]) + const test30 = '@use ./test1,./test2' + const res = parseImportsNext(test30).imports + expect(res).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) + const test31 = '@use ./test1,./test2;' + expect(parseImportsNext(test31).imports).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) const test32 = '@require ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test32).imports).toMatchObject([ + { type: 'require', path: './test1' }, + { type: 'require', path: './test2' }, + ]) + const test33 = '@require ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test33).imports).toMatchObject([ + { type: 'require', path: './test1' }, + { type: 'require', path: './test2' }, + ]) const test34 = '@import \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImportsNext(test34).imports).toMatchObject([ + { type: 'import', path: './test1' }, + { type: 'import', path: './test2' }, + ]) + const test35 = '@import \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImportsNext(test35).imports).toMatchObject([ + { type: 'import', path: './test1' }, + { type: 'import', path: './test2' }, + ]) const test36 = '@use \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImportsNext(test36).imports).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) + const test37 = '@use \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImportsNext(test37).imports).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) const test38 = '@require \\"./test1\\", \\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImportsNext(test38).imports).toMatchObject([ + { type: 'require', path: './test1' }, + { type: 'require', path: './test2' }, + ]) + const test39 = '@require \\"./test1\\", \\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImportsNext(test39).imports).toMatchObject([ + { type: 'require', path: './test1' }, + { type: 'require', path: './test2' }, + ]) const test40 = '@import \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test40).imports).toMatchObject([ + { type: 'import', path: './test1' }, + { type: 'import', path: './test2' }, + ]) + const test41 = '@import \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test41).imports).toMatchObject([ + { type: 'import', path: './test1' }, + { type: 'import', path: './test2' }, + ]) const test42 = '@use \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test42).imports).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) + const test43 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test43).imports).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) + + const test43_1 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test43_1).imports).toMatchObject([ + { type: 'use', path: './test1' }, + { type: 'use', path: './test2' }, + ]) const test44 = '@require \'./test1\', \'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test44).imports).toMatchObject([ + { type: 'require', path: './test1' }, + { type: 'require', path: './test2' }, + ]) const test45 = '@require \'./test1\', \'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImportsNext(test45).imports).toMatchObject([ + { type: 'require', path: './test1' }, + { type: 'require', path: './test2' }, + ]) const test46 = '@requiretest\\"' // ', " 报错 - const test47 = 'e@requireete\\"st' // ', " 报错 + expect(() => parseImportsNext(test46)).toThrowError('syntax error: unmatched quotes') + const test49 = '@requiretest\'' // ', " 报错 + expect(() => parseImportsNext(test49)).toThrowError('syntax error: unmatched quotes') + + const test47 = 'e@require\\"st' // ', " 报错 + expect(() => parseImportsNext(test47)).toThrowError('syntax error') + + const test53 = '@requiretests @require test' // 解析 + expect(() => parseImportsNext(test53)).toThrowError('syntax error: unknown At Rule') + const test48 = '@requ\\"iretest' // ', " 报错 + expect(() => parseImportsNext(test48)).toThrowError('syntax error') - const test49 = '@requiretest\'' // ', " 报错 const test50 = 'e@requireete\'st' // ', " 报错 + expect(() => parseImportsNext(test50)).toThrowError('syntax error') const test51 = '@requ\'iretest' // ', " 报错 + expect(() => parseImportsNext(test51)).toThrowError('syntax error') + + const test52 = '@require \'teasd' // ', " 报错 + expect(() => parseImportsNext(test52)).toThrowError('syntax error: unmatched quotes') - const test52 = 'adwad @require test' // 忽略@前内容 - expect(parseImportsNext(test52).imports).toMatchObject([{ type: 'require', path: 'test' }]) + const test52_1 = 'adwad @require testadwad' // 忽略@前内容 + expect(parseImportsNext(test52_1).imports).toMatchObject([{ type: 'require', path: 'testadwad' }]) - const test53 = '@requiretest @require test' // 解析 - expect(parseImportsNext(test53).imports).toMatchObject([{ type: 'require', path: 'test' }]) + const test54 = '@require tea\'sd' // ', " 报错 + expect(() => { parseImportsNext(test54) }).toThrowError('syntax error: unmatched quotes') + + const test55 = '@require teasd\'' // ', " 报错 + expect(() => parseImportsNext(test55)).toThrowError('syntax error: unmatched quotes') + + const test56 = '@require \\"teasd' // ', " 报错 + expect(() => parseImportsNext(test56)).toThrowError('syntax error: unmatched quotes') + + const test57 = '@require tea\\"sd' // ', " + expect(() => parseImportsNext(test57)).toThrowError('syntax error: unmatched quotes') + + const test58 = '@require teasd\\"' // ', " 报错 TODO + expect(() => parseImportsNext(test58)).toThrowError('syntax error: unmatched quotes') + + const test59 = '@at-root teasd;@require teasd' // 报错 + expect(parseImportsNext(test59).imports).toMatchObject([ + { type: 'require', path: 'teasd' } + ]) + // TODO:引号 和 无引号混合 // TODO:注释 }) }) diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts index 6745633..bf2f0ba 100644 --- a/packages/core/parser/parser-import-next.ts +++ b/packages/core/parser/parser-import-next.ts @@ -1,9 +1,13 @@ +const innerAtRule = 'media,extend,at-root,debug,warn,forward,mixin,include,function,error' export enum ParserState { Initial, - At, + AtStart, + AtEnd, AtImport, AtUse, AtRequire, + QuotesStart, + QuotesEnd, StringLiteral, } @@ -15,6 +19,7 @@ export interface ImportStatement { suffix?: string } const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\]/g, '') + export function parseImportsNext(content: string): { imports: ImportStatement[] getCurState: () => ParserState @@ -31,69 +36,137 @@ export function parseImportsNext(content: string): { switch (state) { case ParserState.Initial: if (char === '@') - state = ParserState.At + state = ParserState.AtStart + break - case ParserState.At: - AtPath = AtPath + char - if (AtPath === 'import') { - AtPath = '' - state = ParserState.AtImport - currentImport = { type: 'import', path: '' } - } else if (AtPath === 'use') { - AtPath = '' - state = ParserState.AtUse - currentImport = { type: 'use', path: '' } - } else if (AtPath === 'require') { - AtPath = '' - state = ParserState.AtRequire - currentImport = { type: 'require', path: '' } + case ParserState.AtStart: + if (/[A-Za-z]$/.test(char)) + AtPath = AtPath + char + else + state = ParserState.AtEnd + + if (i === source.length - 1) { + if (char === '"' || char === "'") + throw new Error('syntax error: unmatched quotes') + else + walkContentEnd(i) } - // '@importtest' 直接回到 Initial 不再处理 - if ((state !== ParserState.At && source[i + 1] !== ' ') - || i === source.length - 1) { - state = ParserState.Initial - currentImport = undefined + if (!(/[A-Za-z]$/.test(char)) + && char !== '\n' + && char !== ' ' + && char !== '-') + throw new Error('syntax error') + + break + case ParserState.AtEnd: + if (char !== '\n' && char !== ' ' && char !== '-') { + if (AtPath === 'import') { + AtPath = '' + state = ParserState.AtImport + currentImport = { type: 'import', path: '' } + i-- + } else if (AtPath === 'use') { + AtPath = '' + state = ParserState.AtUse + currentImport = { type: 'use', path: '' } + i-- + } else if (AtPath === 'require') { + AtPath = '' + state = ParserState.AtRequire + currentImport = { type: 'require', path: '' } + i-- + } else { + if (!innerAtRule.includes(AtPath)) + throw new Error('syntax error: unknown At Rule') + + AtPath = '' + state = ParserState.Initial + } } break case ParserState.AtImport: case ParserState.AtUse: case ParserState.AtRequire: - // 当字符不是空格,且前一个是空格,进入取值 - if (char !== ' ' && source[i - 1] === ' ') { - debugger - state = ParserState.StringLiteral + // '@require test.css;@require test2.css' + if (char === '@' && !(/[A-Za-z]$/.test(source[i - 1]))) { + i-- + state = ParserState.Initial + break + } + + if (char === "'" || char === '"') { + currentImport!.start = i + state = ParserState.QuotesStart + break + } + if (char !== '\n' && char !== ' ') { currentImport!.start = i currentImport!.path += char - } else if (char === '\n' || i === source.length - 1) { + state = ParserState.StringLiteral + break + } + break + case ParserState.QuotesStart: + if (char === "'" || char === '"') { + currentImport!.end = i + state = ParserState.QuotesEnd + if (i === source.length - 1) + walkContentEnd(i) + break + } + if (i === source.length - 1) + throw new Error('syntax error: unmatched quotes') + + currentImport!.path += char + break + case ParserState.QuotesEnd: + if (i === source.length - 1) { walkContentEnd(i) + } else { + i-- + state = ParserState.StringLiteral } break case ParserState.StringLiteral: - // 遇到引号,且起始位置也是,则是有引号状态下的取值结束 - if ((char === "'" || char === '"') - && (currentImport!.start || currentImport!.start === 0) - && (source[currentImport!.start] === char)) { - if (currentImport!.type === 'import') - state = ParserState.AtImport - if (currentImport!.type === 'use') + // '@require test.css@require test2.css' + // '@import ./test1, ./test2' + if ( + char !== '@' + && (char === ' ' + || char === ',' + || char === '"' + || char === "'" + || char === ';' + || char === '\n')) { + const curType = currentImport?.type + walkContentEnd(i) + if (curType === 'import') { + state = ParserState.AtImport + currentImport = { type: 'import', path: '' } + } else if (curType === 'use') { state = ParserState.AtUse - - if (currentImport!.type === 'require') + currentImport = { type: 'use', path: '' } + } else if (curType === 'require') { state = ParserState.AtRequire + currentImport = { type: 'require', path: '' } + } - currentImport!.path += char - } else { - // 取值 - currentImport!.path += char + if (char === "'" || char === '"') { + currentImport!.start = i + state = ParserState.QuotesStart + if (i === source.length - 1 && (char === '"' || char === "'")) { + throw new Error('syntax error: unmatched quotes') + } + break + } + + break } - // @require test.css 引号的情况会,会一直 - // StringLiteral 到结束 - // TODO:需要标记一下没引号情况,然后在外部处理 - // (不要再解析器里处理,这不是它的工作,对字符串任何改变都会影响 start、end) + currentImport!.path += char if (i === source.length - 1) walkContentEnd(i) @@ -103,12 +176,16 @@ export function parseImportsNext(content: string): { } function walkContentEnd(index: number) { + pushCurrentImport(index) + state = ParserState.Initial + } + + function pushCurrentImport(index: number) { if (currentImport && currentImport.start !== undefined) { currentImport.end = index imports.push(currentImport) currentImport = undefined } - state = ParserState.Initial } function getCurState() { From 304323b2ea165bf3adfb40af927a75b539fe454f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=9B=BE=E4=B8=89=E8=AF=AD?= <32354856+baiwusanyu-c@users.noreply.github.com> Date: Mon, 27 Mar 2023 21:30:45 +0800 Subject: [PATCH 07/10] chore: temp commit --- packages/core/css/pre-process-css.ts | 2 +- .../parser/__test__/parser-import.spec.ts | 697 ++++++++++-------- packages/core/parser/parser-import-next.ts | 31 +- 3 files changed, 413 insertions(+), 317 deletions(-) diff --git a/packages/core/css/pre-process-css.ts b/packages/core/css/pre-process-css.ts index 6bc3d39..2a5590f 100644 --- a/packages/core/css/pre-process-css.ts +++ b/packages/core/css/pre-process-css.ts @@ -16,7 +16,7 @@ import MagicString from 'magic-string' import sass from 'sass' import less from 'less' import stylus from 'stylus' -import { parseImports } from '../parser/parser-import' +import { parseImports } from '../parser/parser-import-next' import type { ImportStatement } from '../parser/parser-import' import type { ICSSFileMap, SearchGlobOptions } from '../types' diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 8a1e719..5ad283a 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -1,132 +1,7 @@ import { describe, expect, test } from 'vitest' -import { ParserState, parseImports } from '../parser-import' -import { parseImportsNext } from '../parser-import-next' +import { ParserState, parseImports } from '../parser-import-next' describe('parse import', () => { - /* test('parseImports: Initial -> At', () => { - const { getCurState } = parseImports('@') - expect(getCurState()).toBe(ParserState.At) - }) - - test('parseImports: At -> AtImport', () => { - const { getCurState } = parseImports('@i') - expect(getCurState()).toBe(ParserState.AtImport) - }) - - test('parseImports: At -> AtUse', () => { - const { getCurState } = parseImports('@u') - expect(getCurState()).toBe(ParserState.AtUse) - }) - - test('parseImports: At -> AtRequire', () => { - const { getCurState } = parseImports('@r') - expect(getCurState()).toBe(ParserState.AtRequire) - }) - - test('parseImports: At -> Initial', () => { - const { getCurState } = parseImports('@a') - expect(getCurState()).toBe(ParserState.Initial) - }) - - test('parseImports: AtUse -> Initial', () => { - const { getCurState } = parseImports('@use;') - expect(getCurState()).toBe(ParserState.Initial) - }) - - test('parseImports: AtImport -> Initial', () => { - const { getCurState } = parseImports('@import;') - expect(getCurState()).toBe(ParserState.Initial) - }) - - test('parseImports: AtRequire -> Initial', () => { - const { getCurState } = parseImports('@require;') - expect(getCurState()).toBe(ParserState.Initial) - }) - - test('parseImports: AtUse -> StringLiteral', () => { - const { getCurState, getCurImport } = parseImports('@use "') - expect(getCurState()).toBe(ParserState.StringLiteral) - expect(getCurImport()).toMatchObject({ type: 'use', path: '"', start: 5 }) - - const { getCurState: getCurState1, getCurImport: getCurImport1 } = parseImports('@use \'') - expect(getCurState1()).toBe(ParserState.StringLiteral) - expect(getCurImport1()).toMatchObject({ type: 'use', path: '\'', start: 5 }) - - const { getCurState: getCurState2, getCurImport: getCurRequire1 } = parseImports('@require \'') - expect(getCurState2()).toBe(ParserState.StringLiteral) - expect(getCurRequire1()).toMatchObject({ type: 'require', path: '\'', start: 9 }) - }) - - test('parseImports: StringLiteral -> concat string', () => { - const { getCurState, getCurImport } = parseImports('@import "test') - expect(getCurState()).toBe(ParserState.StringLiteral) - expect(getCurImport()).toMatchObject({ type: 'import', path: '"test', start: 8 }) - - const { getCurState: getCurState1, getCurImport: getCurImport1 } = parseImports('@use "test') - expect(getCurState1()).toBe(ParserState.StringLiteral) - expect(getCurImport1()).toMatchObject({ type: 'use', path: '"test', start: 5 }) - - const { getCurState: getCurState2, getCurImport: getCurRequire1 } = parseImports('@require "test') - expect(getCurState2()).toBe(ParserState.StringLiteral) - expect(getCurRequire1()).toMatchObject({ type: 'require', path: '"test', start: 9 }) - }) - - test('parseImports: AtImport -> end', () => { - const { - imports: imports1, - getCurState: getCurState1, - getCurImport: getCurImport1, - } = parseImports('@use "test";') - expect(getCurState1()).toBe(ParserState.Initial) - expect(getCurImport1()).toBe(undefined) - expect(imports1).toMatchObject([{ type: 'use', path: '"test"', start: 5, end: 11 }]) - - const { - imports: imports2, - getCurState: getCurState2, - getCurImport: getCurImport2, - } = parseImports('@use "test"') - expect(getCurState2()).toBe(ParserState.AtUse) - expect(getCurImport2()).toMatchObject({ type: 'use', path: '"test"', start: 5 }) - expect(imports2.length).toBe(0) - - const { - imports: imports4, - getCurState: getCurState4, - getCurImport: getCurImport4, - } = parseImports('@import "test";') - expect(getCurState4()).toBe(ParserState.Initial) - expect(getCurImport4()).toBe(undefined) - expect(imports4).toMatchObject([{ type: 'import', path: '"test"', start: 8, end: 14 }]) - - const { - imports: imports3, - getCurState: getCurState3, - getCurImport: getCurImport3, - } = parseImports('@import "test"') - expect(getCurState3()).toBe(ParserState.AtImport) - expect(getCurImport3()).toMatchObject({ type: 'import', path: '"test"', start: 8 }) - expect(imports3.length).toBe(0) - - const { - imports: imports5, - getCurState: getCurState5, - getCurImport: getCurImport5, - } = parseImports('@require "test";') - expect(getCurState5()).toBe(ParserState.Initial) - expect(getCurImport5()).toBe(undefined) - expect(imports5).toMatchObject([{ type: 'require', path: '"test"', start: 9, end: 15 }]) - - const { - imports: imports6, - getCurState: getCurState6, - getCurImport: getCurImport6, - } = parseImports('@require "test"') - expect(getCurState6()).toBe(ParserState.AtRequire) - expect(getCurImport6()).toMatchObject({ type: 'require', path: '"test"', start: 9 }) - expect(imports6.length).toBe(0) - }) - test('parseImports: basic', () => { const { imports, @@ -134,15 +9,16 @@ describe('parse import', () => { getCurImport, } = parseImports('@import "./test";\n' + '@use \'./test-use\';\n' - + '@require \'./test-require\';\n' - + '#app {\n' - + ' div {\n' - + ' color: v-bind(fooColor);\n' - + ' }\n' - + ' .foo {\n' - + ' color: red\n' - + ' }\n' - + '}') + + '@require \'./test-require\';\n') + // + '#app {\n' + // + ' div {\n' + // + ' color: v-bind(fooColor);\n' + // + ' }\n' + // + ' .foo {\n' + // + ' color: red\n' + // + ' }\n' + // + '}') + console.log(imports) expect(getCurState()).toBe(ParserState.Initial) expect(getCurImport()).toBe(undefined) expect(imports).toMatchObject([ @@ -150,301 +26,500 @@ describe('parse import', () => { { type: 'use', path: '\'./test-use\'', start: 23, end: 35 }, { type: 'require', path: '\'./test-require\'', start: 46, end: 62 }, ]) - }) */ - - test('parseImports: TESSSSSST', () => { - const test1 = ' @import \'./test.css\';' - expect(parseImportsNext(test1).imports).toMatchObject([{ type: 'import', path: './test.css' }]) + }) + test('parseImports: test1', () => { + const test1 = ' @import \'./test.css\';' + expect(parseImports(test1).imports).toMatchObject([{ type: 'import', path: './test.css' }]) + }) + test('parseImports: test2', () => { const test2 = '@import \'test\';\n@import \'test2\';\n' - expect(parseImportsNext(test2).imports).toMatchObject([{ type: 'import', path: 'test' }, { type: 'import', path: 'test2' }]) - + expect(parseImports(test2).imports).toMatchObject([{ type: 'import', path: 'test' }, { type: 'import', path: 'test2' }]) + }) + test('parseImports: test3', () => { const test3 = '@import \\"test.css\\";' - expect(parseImportsNext(test3).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) - + expect(parseImports(test3).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) + }) + test('parseImports: test4', () => { const test4 = '@import \\"test\\";\n' - expect(parseImportsNext(test4).imports).toMatchObject([{ type: 'import', path: 'test' }]) + expect(parseImports(test4).imports).toMatchObject([{ type: 'import', path: 'test' }]) + }) + test('parseImports: test5', () => { const test5 = '@import \'test.css\'\n' - expect(parseImportsNext(test5).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) + expect(parseImports(test5).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) + }) + test('parseImports: test6', () => { const test6 = '@import \'test\'\n' - expect(parseImportsNext(test6).imports).toMatchObject([{ type: 'import', path: 'test' }]) + expect(parseImports(test6).imports).toMatchObject([{ type: 'import', path: 'test' }]) + }) + test('parseImports: test7', () => { const test7 = '@import \\"test.css\\"' - expect(parseImportsNext(test7).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) + expect(parseImports(test7).imports).toMatchObject([{ type: 'import', path: 'test.css' }]) + }) + test('parseImports: test8', () => { const test8 = '@import \\"test\\"' - expect(parseImportsNext(test8).imports).toMatchObject([{ type: 'import', path: 'test' }]) + expect(parseImports(test8).imports).toMatchObject([{ type: 'import', path: 'test' }]) + }) + test('parseImports: test9', () => { const test9 = '@importB' // 不解析 - expect(parseImportsNext(test9).imports).toMatchObject([]) + expect(parseImports(test9).imports).toMatchObject([]) + }) + test('parseImports: test10', () => { const test10 = '@use \'test.css\';' - expect(parseImportsNext(test10).imports).toMatchObject([{ type: 'use', path: 'test.css' }]) - + expect(parseImports(test10).imports).toMatchObject([{ type: 'use', path: 'test.css' }]) + }) + test('parseImports: test11', () => { const test11 = '@use \'test\';' - expect(parseImportsNext(test11).imports).toMatchObject([{ type: 'use', path: 'test' }]) - + expect(parseImports(test11).imports).toMatchObject([{ type: 'use', path: 'test' }]) + }) + test('parseImports: test12', () => { const test12 = '@use \\"./test.css\\";' - expect(parseImportsNext(test12).imports).toMatchObject([{ type: 'use', path: './test.css' }]) - + expect(parseImports(test12).imports).toMatchObject([{ type: 'use', path: './test.css' }]) + }) + test('parseImports: test13', () => { const test13 = '@use \\"./test\\";\n' - expect(parseImportsNext(test13).imports).toMatchObject([{ type: 'use', path: './test' }]) - + expect(parseImports(test13).imports).toMatchObject([{ type: 'use', path: './test' }]) + }) + test('parseImports: test14', () => { const test14 = '@use \'test.css\'' - expect(parseImportsNext(test14).imports).toMatchObject([{ type: 'use', path: 'test.css' }]) - + expect(parseImports(test14).imports).toMatchObject([{ type: 'use', path: 'test.css' }]) + }) + test('parseImports: test15', () => { const test15 = '@use \'test\'' - expect(parseImportsNext(test15).imports).toMatchObject([{ type: 'use', path: 'test' }]) - + expect(parseImports(test15).imports).toMatchObject([{ type: 'use', path: 'test' }]) + }) + test('parseImports: test16', () => { const test16 = '@use \\"./test.css\\"' - expect(parseImportsNext(test16).imports).toMatchObject([{ type: 'use', path: './test.css' }]) - + expect(parseImports(test16).imports).toMatchObject([{ type: 'use', path: './test.css' }]) + }) + test('parseImports: test17', () => { const test17 = '@use \\"test\\"' - expect(parseImportsNext(test17).imports).toMatchObject([{ type: 'use', path: 'test' }]) - + expect(parseImports(test17).imports).toMatchObject([{ type: 'use', path: 'test' }]) + }) + test('parseImports: test18', () => { const test18 = '@usetest' // 不解析 - expect(parseImportsNext(test18).imports).toMatchObject([]) - + expect(parseImports(test18).imports).toMatchObject([]) + }) + test('parseImports: test19', () => { const test19 = '@require \'test.css\';' // { type: 'import', path: '\'./test.css\''} - expect(parseImportsNext(test19).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) - + expect(parseImports(test19).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) + }) + test('parseImports: test20', () => { const test20 = '@require \'test\';' // { type: 'import', path: '\'./test\''} - expect(parseImportsNext(test20).imports).toMatchObject([{ type: 'require', path: 'test' }]) - + expect(parseImports(test20).imports).toMatchObject([{ type: 'require', path: 'test' }]) + }) + test('parseImports: test21', () => { const test21 = '@require \\"test.css\\";' // { type: 'import', path: '\\"./test.css\\"'} - expect(parseImportsNext(test21).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) - - const test22 = '@require \\"test\\";' // { type: 'import', path: '\\"./test\\"'} - expect(parseImportsNext(test22).imports).toMatchObject([{ type: 'require', path: 'test' }]) - - const test23 = '@require \'test.css\'' // { type: 'import', path: '\'./test.css\''} - expect(parseImportsNext(test23).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) - - const test24 = '@require \'test\'' // { type: 'import', path: '\'./test\''} - expect(parseImportsNext(test24).imports).toMatchObject([{ type: 'require', path: 'test' }]) - - const test25 = '@require \\"test.css\\"' // { type: 'import', path: '\\"./test.css\\"'} - expect(parseImportsNext(test25).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) - - const test26 = '@require \\"test\\"' // { type: 'import', path: '\\"./test\\"'} - expect(parseImportsNext(test26).imports).toMatchObject([{ type: 'require', path: 'test' }]) - - const test27 = '@requiretest' // 不解析 - expect(parseImportsNext(test27).imports).toMatchObject([]) - - const test28_1 = '@require test.css' - expect(parseImportsNext(test28_1).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) - - const test28_2 = '@require ./test' - expect(parseImportsNext(test28_2).imports).toMatchObject([{ type: 'require', path: './test' }]) - - const test28_3 = '@require test;' - expect(parseImportsNext(test28_3).imports).toMatchObject([{ type: 'require', path: 'test' }]) - - const test28_4 = '@require ./test\n' - expect(parseImportsNext(test28_4).imports).toMatchObject([{ type: 'require', path: './test' }]) - - - const test28_5 = '@require test.css@require test2.css' - expect(parseImportsNext(test28_5).imports).toMatchObject([ + expect(parseImports(test21).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) + }) + test('parseImports: test22', () => { + const test22 = '@require \\"test\\";' + expect(parseImports(test22).imports).toMatchObject([{ type: 'require', path: 'test' }]) + }) + test('parseImports: test23', () => { + const test23 = '@require \'test.css\'' + expect(parseImports(test23).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) + }) + test('parseImports: test24', () => { + const test24 = '@require \'test\'' + expect(parseImports(test24).imports).toMatchObject([{ type: 'require', path: 'test' }]) + }) + test('parseImports: test25', () => { + const test25 = '@require \\"test.css\\"' + expect(parseImports(test25).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) + }) + test('parseImports: test26', () => { + const test26 = '@require \\"test\\"' + expect(parseImports(test26).imports).toMatchObject([{ type: 'require', path: 'test' }]) + }) + test('parseImports: test27', () => { + const test27 = '@requiretest' + expect(parseImports(test27).imports).toMatchObject([]) + }) + test('parseImports: test28', () => { + const test28 = '@require test.css' + expect(parseImports(test28).imports).toMatchObject([{ type: 'require', path: 'test.css' }]) + }) + test('parseImports: test29', () => { + const test29 = '@require ./test' + expect(parseImports(test29).imports).toMatchObject([{ type: 'require', path: './test' }]) + }) + test('parseImports: test30', () => { + const test30 = '@require test;' + expect(parseImports(test30).imports).toMatchObject([{ type: 'require', path: 'test' }]) + }) + test('parseImports: test31', () => { + const test31 = '@require ./test\n' + expect(parseImports(test31).imports).toMatchObject([{ type: 'require', path: './test' }]) + }) + test('parseImports: test32', () => { + const test32 = '@require test.css@require test2.css' + expect(parseImports(test32).imports).toMatchObject([ { type: 'require', path: 'test.css@require' }, { type: 'require', path: 'test2.css' }, ]) - - const test28_6 = '@require test.css,@require test2.css' - expect(parseImportsNext(test28_6).imports).toMatchObject([ + }) + test('parseImports: test33', () => { + const test33 = '@require test.css,@require test2.css' + expect(parseImports(test33).imports).toMatchObject([ { type: 'require', path: 'test.css' }, { type: 'require', path: 'test2.css' }, ]) - - const test28_7 = '@require test.css;@require test2.css' - expect(parseImportsNext(test28_7).imports).toMatchObject([ + }) + test('parseImports: test34', () => { + const test34 = '@require test.css;@require test2.css' + expect(parseImports(test34).imports).toMatchObject([ { type: 'require', path: 'test.css' }, { type: 'require', path: 'test2.css' }, ]) - - const test28_9 = '@require test.css; @require test2.css' - expect(parseImportsNext(test28_9).imports).toMatchObject([ + }) + test('parseImports: test35', () => { + const test35 = '@require test.css; @require test2.css' + expect(parseImports(test35).imports).toMatchObject([ { type: 'require', path: 'test.css' }, { type: 'require', path: 'test2.css' }, ]) - - const test28_8 = '@require test.css, @require test2.css' - expect(parseImportsNext(test28_8).imports).toMatchObject([ + }) + test('parseImports: test36', () => { + const test36 = '@require test.css, @require test2.css' + expect(parseImports(test36).imports).toMatchObject([ { type: 'require', path: 'test.css' }, { type: 'require', path: 'test2.css' }, ]) - - const test28_10 = '@require test.css @require test2.css' - expect(parseImportsNext(test28_10).imports).toMatchObject([ + }) + test('parseImports: test37', () => { + const test37 = '@require test.css @require test2.css' + expect(parseImports(test37).imports).toMatchObject([ { type: 'require', path: 'test.css' }, { type: 'require', path: 'test2.css' }, ]) - - const test28_11 = '@require test.css @use test2.css' - expect(parseImportsNext(test28_11).imports).toMatchObject([ + }) + test('parseImports: test38', () => { + const test38 = '@require test.css @use test2.css' + expect(parseImports(test38).imports).toMatchObject([ { type: 'require', path: 'test.css' }, { type: 'use', path: 'test2.css' }, ]) - - const test28 = '@import ./test1, ./test2' - expect(parseImportsNext(test28).imports).toMatchObject([ + }) + test('parseImports: test39', () => { + const test39 = '@import ./test1, ./test2' + expect(parseImports(test39).imports).toMatchObject([ { type: 'import', path: './test1' }, { type: 'import', path: './test2' }, ]) - - const test29 = '@import ./test1, ./test2;\n' - expect(parseImportsNext(test29).imports).toMatchObject([ + }) + test('parseImports: test40', () => { + const test40 = '@import ./test1, ./test2;\n' + expect(parseImports(test40).imports).toMatchObject([ { type: 'import', path: './test1' }, { type: 'import', path: './test2' }, ]) - const test30 = '@use ./test1,./test2' - const res = parseImportsNext(test30).imports + }) + test('parseImports: test41', () => { + const test41 = '@use ./test1,./test2' + const res = parseImports(test41).imports expect(res).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - const test31 = '@use ./test1,./test2;' - expect(parseImportsNext(test31).imports).toMatchObject([ + }) + test('parseImports: test42', () => { + const test42 = '@use ./test1,./test2;' + expect(parseImports(test42).imports).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - const test32 = '@require ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test32).imports).toMatchObject([ + }) + test('parseImports: test43', () => { + const test43 = '@require ./test1,./test2'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test43).imports).toMatchObject([ { type: 'require', path: './test1' }, { type: 'require', path: './test2' }, ]) - - const test33 = '@require ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test33).imports).toMatchObject([ + }) + test('parseImports: test44', () => { + const test44 = '@require ./test1,./test2;'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test44).imports).toMatchObject([ { type: 'require', path: './test1' }, { type: 'require', path: './test2' }, ]) - - const test34 = '@import \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} - expect(parseImportsNext(test34).imports).toMatchObject([ + }) + test('parseImports: test45', () => { + const test45 = '@import \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImports(test45).imports).toMatchObject([ { type: 'import', path: './test1' }, { type: 'import', path: './test2' }, ]) - - const test35 = '@import \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} - expect(parseImportsNext(test35).imports).toMatchObject([ + }) + test('parseImports: test46', () => { + const test46 = '@import \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImports(test46).imports).toMatchObject([ { type: 'import', path: './test1' }, { type: 'import', path: './test2' }, ]) - - const test36 = '@use \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} - expect(parseImportsNext(test36).imports).toMatchObject([ + }) + test('parseImports: test47', () => { + const test47 = '@use \\"./test1\\",\\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImports(test47).imports).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - - const test37 = '@use \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} - expect(parseImportsNext(test37).imports).toMatchObject([ + }) + test('parseImports: test48', () => { + const test48 = '@use \\"./test1\\",\\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImports(test48).imports).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - - const test38 = '@require \\"./test1\\", \\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} - expect(parseImportsNext(test38).imports).toMatchObject([ + }) + test('parseImports: test49', () => { + const test49 = '@require \\"./test1\\", \\"./test2\\"' // { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImports(test49).imports).toMatchObject([ { type: 'require', path: './test1' }, { type: 'require', path: './test2' }, ]) - - const test39 = '@require \\"./test1\\", \\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} - expect(parseImportsNext(test39).imports).toMatchObject([ + }) + test('parseImports: test50', () => { + const test50 = '@require \\"./test1\\", \\"./test2\\";'// { type: 'import', path: '\\"./test\\"'}, { type: 'import', path: '\\"./test2\\"'} + expect(parseImports(test50).imports).toMatchObject([ { type: 'require', path: './test1' }, { type: 'require', path: './test2' }, ]) - - const test40 = '@import \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test40).imports).toMatchObject([ + }) + test('parseImports: test51', () => { + const test51 = '@import \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test51).imports).toMatchObject([ { type: 'import', path: './test1' }, { type: 'import', path: './test2' }, ]) - - const test41 = '@import \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test41).imports).toMatchObject([ + }) + test('parseImports: test52', () => { + const test52 = '@import \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test52).imports).toMatchObject([ { type: 'import', path: './test1' }, { type: 'import', path: './test2' }, ]) - - const test42 = '@use \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test42).imports).toMatchObject([ + }) + test('parseImports: test53', () => { + const test53 = '@use \'./test1\',\'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test53).imports).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - - const test43 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test43).imports).toMatchObject([ + }) + test('parseImports: test54', () => { + const test54 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test54).imports).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - - const test43_1 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test43_1).imports).toMatchObject([ + }) + test('parseImports: test55', () => { + const test55 = '@use \'./test1\',\'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test55).imports).toMatchObject([ { type: 'use', path: './test1' }, { type: 'use', path: './test2' }, ]) - - const test44 = '@require \'./test1\', \'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test44).imports).toMatchObject([ + }) + test('parseImports: test56', () => { + const test56 = '@require \'./test1\', \'./test2\'' // { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} + expect(parseImports(test56).imports).toMatchObject([ { type: 'require', path: './test1' }, { type: 'require', path: './test2' }, ]) - const test45 = '@require \'./test1\', \'./test2\';'// { type: 'import', path: '\'./test\''}, { type: 'import', path: '\'./test2\''} - expect(parseImportsNext(test45).imports).toMatchObject([ + }) + test('parseImports: test57', () => { + const test57 = '@require \'./test1\', \'./test2\';' + expect(parseImports(test57).imports).toMatchObject([ { type: 'require', path: './test1' }, { type: 'require', path: './test2' }, ]) + }) + test('parseImports: test58', () => { + const test58_1 = '@requiretest\\"' + expect(() => parseImports(test58_1)).toThrowError('syntax error: unmatched quotes') + const test58_2 = '@requiretest\'' + expect(() => parseImports(test58_2)).toThrowError('syntax error: unmatched quotes') + + const test58_3 = 'e@require\\"st' + expect(() => parseImports(test58_3)).toThrowError('syntax error') + + const test58_4 = '@requiretests @require test' + expect(() => parseImports(test58_4)).toThrowError('syntax error: unknown At Rule') + + const test58_5 = '@requ\\"iretest' + expect(() => parseImports(test58_5)).toThrowError('syntax error') + + const test58_6 = 'e@requireete\'st' + expect(() => parseImports(test58_6)).toThrowError('syntax error') + + const test58_7 = '@requ\'iretest' + expect(() => parseImports(test58_7)).toThrowError('syntax error') + + const test58_8 = '@require \'teasd' + expect(() => parseImports(test58_8)).toThrowError('syntax error: unmatched quotes') + + const test58_9 = 'adwad @require testadwad' + expect(parseImports(test58_9).imports).toMatchObject([{ type: 'require', path: 'testadwad' }]) + + const test58_10 = '@require tea\'sd' + expect(() => { parseImports(test58_10) }).toThrowError('syntax error: unmatched quotes') + + const test58_11 = '@require teasd\'' + expect(() => parseImports(test58_11)).toThrowError('syntax error: unmatched quotes') + + const test58_12 = '@require \\"teasd' + expect(() => parseImports(test58_12)).toThrowError('syntax error: unmatched quotes') - const test46 = '@requiretest\\"' // ', " 报错 - expect(() => parseImportsNext(test46)).toThrowError('syntax error: unmatched quotes') - const test49 = '@requiretest\'' // ', " 报错 - expect(() => parseImportsNext(test49)).toThrowError('syntax error: unmatched quotes') + const test58_13 = '@require tea\\"sd' + expect(() => parseImports(test58_13)).toThrowError('syntax error: unmatched quotes') - const test47 = 'e@require\\"st' // ', " 报错 - expect(() => parseImportsNext(test47)).toThrowError('syntax error') + const test58_14 = '@require teasd\\"' + expect(() => parseImports(test58_14)).toThrowError('syntax error: unmatched quotes') - const test53 = '@requiretests @require test' // 解析 - expect(() => parseImportsNext(test53)).toThrowError('syntax error: unknown At Rule') + const test58_15 = '@at-root teasd;@require foo' + expect(parseImports(test58_15).imports).toMatchObject([ + { type: 'require', path: 'foo' }, + ]) + }) + test('parseImports: test59', () => { + const test59 = '@require tea;"sd"' + expect(parseImports(test59).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) + test('parseImports: test60', () => { + const test60 = '@require "tea";sd' + expect(parseImports(test60).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) + test('parseImports: test61', () => { + const test61 = '@require "tea",sd' + expect(parseImports(test61).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) + test('parseImports: test62', () => { + const test62 = '@require tea,"sd"' + expect(parseImports(test62).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) + test('parseImports: test63', () => { + const test63 = '@require tea "sd"' + expect(parseImports(test63).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) + test('parseImports: test64', () => { + const test64 = '@require "sd" tea' + expect(parseImports(test64).imports).toMatchObject([ + { type: 'require', path: 'sd' }, + { type: 'require', path: 'tea' }, + ]) + }) + test('parseImports: test65', () => { + const test65 = '@require \'sd\',tea' + expect(parseImports(test65).imports).toMatchObject([ + { type: 'require', path: 'sd' }, + { type: 'require', path: 'tea' }, + ]) + }) + test('parseImports: test66', () => { + const test66 = '@require sd,\'tea\'' + expect(parseImports(test66).imports).toMatchObject([ + { type: 'require', path: 'sd' }, + { type: 'require', path: 'tea' }, + ]) + }) - const test48 = '@requ\\"iretest' // ', " 报错 - expect(() => parseImportsNext(test48)).toThrowError('syntax error') + test('parseImports: test67', () => { + const test67 = '@require sd;\'tea\'' + expect(parseImports(test67).imports).toMatchObject([ + { type: 'require', path: 'sd' }, + { type: 'require', path: 'tea' }, + ]) + }) - const test50 = 'e@requireete\'st' // ', " 报错 - expect(() => parseImportsNext(test50)).toThrowError('syntax error') - const test51 = '@requ\'iretest' // ', " 报错 - expect(() => parseImportsNext(test51)).toThrowError('syntax error') + test('parseImports: test68', () => { + const test68 = '@require \'tea\';sd' + expect(parseImports(test68).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) - const test52 = '@require \'teasd' // ', " 报错 - expect(() => parseImportsNext(test52)).toThrowError('syntax error: unmatched quotes') + test('parseImports: test69', () => { + const test69 = '// @require \'tea\';sd' // 无输出 + expect(parseImports(test69).imports).toMatchObject([]) + }) - const test52_1 = 'adwad @require testadwad' // 忽略@前内容 - expect(parseImportsNext(test52_1).imports).toMatchObject([{ type: 'require', path: 'testadwad' }]) + test('parseImports: test70', () => { + const test70 = '// @require \'tea\';sd\n@require \'redtea\';suda' + expect(parseImports(test70).imports).toMatchObject([ + { type: 'require', path: 'redtea' }, + { type: 'require', path: 'suda' }, + ]) + }) - const test54 = '@require tea\'sd' // ', " 报错 - expect(() => { parseImportsNext(test54) }).toThrowError('syntax error: unmatched quotes') + test('parseImports: test71', () => { + expect(() => parseImports('@require // \'tea\';sd')) + .toThrowError('syntax error') + }) - const test55 = '@require teasd\'' // ', " 报错 - expect(() => parseImportsNext(test55)).toThrowError('syntax error: unmatched quotes') + test('parseImports: test72', () => { + const test72 = '@require \'tea\';sd\n//@require \'tea\';sd' - const test56 = '@require \\"teasd' // ', " 报错 - expect(() => parseImportsNext(test56)).toThrowError('syntax error: unmatched quotes') + expect(parseImports(test72).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) - const test57 = '@require tea\\"sd' // ', " - expect(() => parseImportsNext(test57)).toThrowError('syntax error: unmatched quotes') + test('parseImports: test73', () => { + const test73 = '/*@require \'tea\';sd\n*/@require \'tea\';sd' - const test58 = '@require teasd\\"' // ', " 报错 TODO - expect(() => parseImportsNext(test58)).toThrowError('syntax error: unmatched quotes') + expect(parseImports(test73).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, + ]) + }) - const test59 = '@at-root teasd;@require teasd' // 报错 - expect(parseImportsNext(test59).imports).toMatchObject([ - { type: 'require', path: 'teasd' } + test('parseImports: test74', () => { + const test74 = '@require \'tea\';sd\n/*@require */' + expect(parseImports(test74).imports).toMatchObject([ + { type: 'require', path: 'tea' }, + { type: 'require', path: 'sd' }, ]) - // TODO:引号 和 无引号混合 - // TODO:注释 + }) + + test('parseImports: test75', () => { + const test75 = '/*@require \'tea\';sd\n//@require */\'tea\';sd' + expect(parseImports(test75).imports).toMatchObject([]) + }) + + test('parseImports: test76', () => { + expect(() => parseImports('@require /* \'tea\';sd')) + .toThrowError('syntax error') + }) + + // TODO:无引号下,\n 或 ; 即结束 + // TODO:有引号下,引号外接\n 或 ; 即结束 + test('parseImports: test77', () => { + const test0 = '@import \'./test.css\'; div{ color: red }' + console.log(parseImports(test0).imports) + expect(parseImports(test0).imports).toMatchObject([{ type: 'import', path: './test.css' }]) }) }) diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts index bf2f0ba..19961bc 100644 --- a/packages/core/parser/parser-import-next.ts +++ b/packages/core/parser/parser-import-next.ts @@ -1,6 +1,8 @@ const innerAtRule = 'media,extend,at-root,debug,warn,forward,mixin,include,function,error' export enum ParserState { Initial, + InlineComment, + Comment, AtStart, AtEnd, AtImport, @@ -20,7 +22,7 @@ export interface ImportStatement { } const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\]/g, '') -export function parseImportsNext(content: string): { +export function parseImports(content: string): { imports: ImportStatement[] getCurState: () => ParserState getCurImport: () => undefined | ImportStatement @@ -37,7 +39,18 @@ export function parseImportsNext(content: string): { case ParserState.Initial: if (char === '@') state = ParserState.AtStart - + if (char === '/' && source[i + 1] === '/') + state = ParserState.InlineComment + if (char === '/' && source[i + 1] === '*') + state = ParserState.Comment + break + case ParserState.InlineComment: + if (char === '\n') + state = ParserState.Initial + break + case ParserState.Comment: + if (char === '*' && source[i + 1] === '/') + state = ParserState.Initial break case ParserState.AtStart: if (/[A-Za-z]$/.test(char)) @@ -51,7 +64,6 @@ export function parseImportsNext(content: string): { else walkContentEnd(i) } - if (!(/[A-Za-z]$/.test(char)) && char !== '\n' && char !== ' ' @@ -61,6 +73,9 @@ export function parseImportsNext(content: string): { break case ParserState.AtEnd: if (char !== '\n' && char !== ' ' && char !== '-') { + if (char === '/') + throw new Error('syntax error') + if (AtPath === 'import') { AtPath = '' state = ParserState.AtImport @@ -96,6 +111,12 @@ export function parseImportsNext(content: string): { break } + if (char === '/' && (source[i + 1] === '/' || source[i + 1] === '*')) { + i-- + state = ParserState.Initial + break + } + if (char === "'" || char === '"') { currentImport!.start = i state = ParserState.QuotesStart @@ -157,9 +178,9 @@ export function parseImportsNext(content: string): { if (char === "'" || char === '"') { currentImport!.start = i state = ParserState.QuotesStart - if (i === source.length - 1 && (char === '"' || char === "'")) { + if (i === source.length - 1 && (char === '"' || char === "'")) throw new Error('syntax error: unmatched quotes') - } + break } From 7d81d59762d7fca6cef74ffe7d440b1f1ab01f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=9B=BE=E4=B8=89=E8=AF=AD?= <32354856+baiwusanyu-c@users.noreply.github.com> Date: Mon, 27 Mar 2023 22:48:31 +0800 Subject: [PATCH 08/10] chore: temp commit --- .../parser/__test__/parser-import.spec.ts | 47 +++++++------------ packages/core/parser/parser-import-next.ts | 10 ++-- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 5ad283a..41f81b4 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -3,28 +3,29 @@ import { ParserState, parseImports } from '../parser-import-next' describe('parse import', () => { test('parseImports: basic', () => { + const input = '@import "./test";\n' + + '@use \'./test-use\';\n' + + '@require \'./test-require\';\n' + + '#app {\n' + + ' div {\n' + + ' color: v-bind(fooColor);\n' + + ' }\n' + + ' .foo {\n' + + ' color: red\n' + + ' }\n' + + '}' const { imports, getCurState, getCurImport, - } = parseImports('@import "./test";\n' - + '@use \'./test-use\';\n' - + '@require \'./test-require\';\n') - // + '#app {\n' - // + ' div {\n' - // + ' color: v-bind(fooColor);\n' - // + ' }\n' - // + ' .foo {\n' - // + ' color: red\n' - // + ' }\n' - // + '}') - console.log(imports) + } = parseImports(input) + expect(getCurState()).toBe(ParserState.Initial) expect(getCurImport()).toBe(undefined) expect(imports).toMatchObject([ - { type: 'import', path: '"./test"', start: 8, end: 16 }, - { type: 'use', path: '\'./test-use\'', start: 23, end: 35 }, - { type: 'require', path: '\'./test-require\'', start: 46, end: 62 }, + { type: 'import', path: './test', start: 8, end: 16 }, + { type: 'use', path: './test-use', start: 23, end: 35 }, + { type: 'require', path: './test-require', start: 46, end: 62 }, ]) }) @@ -392,14 +393,12 @@ describe('parse import', () => { const test59 = '@require tea;"sd"' expect(parseImports(test59).imports).toMatchObject([ { type: 'require', path: 'tea' }, - { type: 'require', path: 'sd' }, ]) }) test('parseImports: test60', () => { const test60 = '@require "tea";sd' expect(parseImports(test60).imports).toMatchObject([ { type: 'require', path: 'tea' }, - { type: 'require', path: 'sd' }, ]) }) test('parseImports: test61', () => { @@ -449,7 +448,6 @@ describe('parse import', () => { const test67 = '@require sd;\'tea\'' expect(parseImports(test67).imports).toMatchObject([ { type: 'require', path: 'sd' }, - { type: 'require', path: 'tea' }, ]) }) @@ -457,7 +455,6 @@ describe('parse import', () => { const test68 = '@require \'tea\';sd' expect(parseImports(test68).imports).toMatchObject([ { type: 'require', path: 'tea' }, - { type: 'require', path: 'sd' }, ]) }) @@ -470,7 +467,6 @@ describe('parse import', () => { const test70 = '// @require \'tea\';sd\n@require \'redtea\';suda' expect(parseImports(test70).imports).toMatchObject([ { type: 'require', path: 'redtea' }, - { type: 'require', path: 'suda' }, ]) }) @@ -484,7 +480,6 @@ describe('parse import', () => { expect(parseImports(test72).imports).toMatchObject([ { type: 'require', path: 'tea' }, - { type: 'require', path: 'sd' }, ]) }) @@ -493,7 +488,6 @@ describe('parse import', () => { expect(parseImports(test73).imports).toMatchObject([ { type: 'require', path: 'tea' }, - { type: 'require', path: 'sd' }, ]) }) @@ -501,7 +495,6 @@ describe('parse import', () => { const test74 = '@require \'tea\';sd\n/*@require */' expect(parseImports(test74).imports).toMatchObject([ { type: 'require', path: 'tea' }, - { type: 'require', path: 'sd' }, ]) }) @@ -514,12 +507,4 @@ describe('parse import', () => { expect(() => parseImports('@require /* \'tea\';sd')) .toThrowError('syntax error') }) - - // TODO:无引号下,\n 或 ; 即结束 - // TODO:有引号下,引号外接\n 或 ; 即结束 - test('parseImports: test77', () => { - const test0 = '@import \'./test.css\'; div{ color: red }' - console.log(parseImports(test0).imports) - expect(parseImports(test0).imports).toMatchObject([{ type: 'import', path: './test.css' }]) - }) }) diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts index 19961bc..bc50566 100644 --- a/packages/core/parser/parser-import-next.ts +++ b/packages/core/parser/parser-import-next.ts @@ -143,7 +143,7 @@ export function parseImports(content: string): { currentImport!.path += char break case ParserState.QuotesEnd: - if (i === source.length - 1) { + if (i === source.length - 1 || char === ';' || char === '\n') { walkContentEnd(i) } else { i-- @@ -151,6 +151,10 @@ export function parseImports(content: string): { } break case ParserState.StringLiteral: + if (char === ';' || char === '\n') { + walkContentEnd(i) + break + } // '@require test.css@require test2.css' // '@import ./test1, ./test2' @@ -159,9 +163,7 @@ export function parseImports(content: string): { && (char === ' ' || char === ',' || char === '"' - || char === "'" - || char === ';' - || char === '\n')) { + || char === "'")) { const curType = currentImport?.type walkContentEnd(i) if (curType === 'import') { From e7240b8d4ac5e440545e177f0f16e25f5c78822d Mon Sep 17 00:00:00 2001 From: baiwusanyu-c <740132583@qq.com> Date: Tue, 28 Mar 2023 10:21:40 +0800 Subject: [PATCH 09/10] feat: Generic importer parser --- package.json | 1 - .../core/css/__test__/pre-process-css.spec.ts | 13 + packages/core/css/pre-process-css.ts | 24 +- .../parser/__test__/parser-import.spec.ts | 2 +- packages/core/parser/parser-import-next.ts | 225 ------------------ packages/core/parser/parser-import.ts | 205 +++++++++++++--- play/src/App.vue | 3 +- play/src/assets/sass/foo.sass | 2 +- 8 files changed, 202 insertions(+), 273 deletions(-) delete mode 100644 packages/core/parser/parser-import-next.ts diff --git a/package.json b/package.json index 26d3da0..9f2a172 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ } }, "scripts": { - "pa": "esno packages/core/parser/parser-import.ts", "init": "pnpm i", "lint:fix": "eslint --fix ./ --ext .vue,.js,.ts,.jsx,.tsx,.json ", "dev": "pnpm run --filter @unplugin-vue-cssvars/build dev", diff --git a/packages/core/css/__test__/pre-process-css.spec.ts b/packages/core/css/__test__/pre-process-css.spec.ts index 24d07f2..a3563fe 100644 --- a/packages/core/css/__test__/pre-process-css.spec.ts +++ b/packages/core/css/__test__/pre-process-css.spec.ts @@ -9,6 +9,7 @@ import { getCurFileContent, preProcessCSS, setImportToCompileRes, + transformQuotes, walkCSSTree, } from '../pre-process-css' import type { ImportStatement } from '../../parser/parser-import' @@ -611,4 +612,16 @@ describe('pre process css', () => { expect(delTransformSymbol(res)).toBe(delTransformSymbol(mockStylContent)) expect(delTransformSymbol(res)).toMatchSnapshot() }) + + test('transformQuotes', () => { + const testCases = [ + { input: 'hello', expected: '"hello"' }, + { input: '"hello"', expected: '"hello"' }, + { input: "'world'", expected: '"world"' }, + ] + testCases.forEach(({ input, expected }) => { + const result = transformQuotes({ path: input } as any) + expect(result.path).toBe(expected) + }) + }) }) diff --git a/packages/core/css/pre-process-css.ts b/packages/core/css/pre-process-css.ts index 2a5590f..b39ca88 100644 --- a/packages/core/css/pre-process-css.ts +++ b/packages/core/css/pre-process-css.ts @@ -16,7 +16,7 @@ import MagicString from 'magic-string' import sass from 'sass' import less from 'less' import stylus from 'stylus' -import { parseImports } from '../parser/parser-import-next' +import { parseImports } from '../parser/parser-import' import type { ImportStatement } from '../parser/parser-import' import type { ICSSFileMap, SearchGlobOptions } from '../types' @@ -200,13 +200,13 @@ export function generateCSSCode(path: string, suffix: string) { const code = fs.readFileSync(path, { encoding: 'utf-8' }) let res = '' switch (suffix) { - case `.${SUPPORT_FILE.SCSS}`: // scss / sass + case `.${SUPPORT_FILE.SCSS}`: // scss // @import 有 css 和 scss的同名文件,会编译 scss // @import 编译 scss,会一直编译,一直到遇到 import 了一个 css 或没有 import 为止 // 这里先分析出 imports,在根据其内容将 sass 中 import 删除 // 编译 sass 为 css,再复原 // eslint-disable-next-line no-case-declarations - const parseScssImporter = parseImports(code) + const parseScssImporter = parseImports(code, [transformQuotes]) // eslint-disable-next-line no-case-declarations const codeScssNoImporter = getCurFileContent(code, parseScssImporter.imports) // eslint-disable-next-line no-case-declarations @@ -215,7 +215,7 @@ export function generateCSSCode(path: string, suffix: string) { break case `.${SUPPORT_FILE.SASS}`: // sass // eslint-disable-next-line no-case-declarations - const parseSassImporter = parseImports(code) + const parseSassImporter = parseImports(code, [transformQuotes]) // eslint-disable-next-line no-case-declarations const codeNoImporter = getCurFileContent(code, parseSassImporter.imports) // eslint-disable-next-line no-case-declarations @@ -224,7 +224,7 @@ export function generateCSSCode(path: string, suffix: string) { break case `.${SUPPORT_FILE.LESS}`: // less // eslint-disable-next-line no-case-declarations - const parseLessImporter = parseImports(code) + const parseLessImporter = parseImports(code, [transformQuotes]) // eslint-disable-next-line no-case-declarations const codeLessNoImporter = getCurFileContent(code, parseLessImporter.imports) less.render(codeLessNoImporter, {}, (error, output) => { @@ -236,7 +236,7 @@ export function generateCSSCode(path: string, suffix: string) { break case `.${SUPPORT_FILE.STYL}`: // stylus // eslint-disable-next-line no-case-declarations - const parseStylusImporter = parseImports(code) + const parseStylusImporter = parseImports(code, [transformQuotes]) // eslint-disable-next-line no-case-declarations const codeStylusNoImporter = getCurFileContent(code, parseStylusImporter.imports) stylus.render(codeStylusNoImporter, {}, (error: Error, css: string) => { @@ -266,7 +266,7 @@ export function getCurFileContent(content: string, parseRes: ImportStatement[]) mgcStr.replaceAll('@require', '') } }) - return mgcStr.toString() + return mgcStr.toString().trimStart() } export function setImportToCompileRes(content: string, parseRes: ImportStatement[]) { @@ -277,3 +277,13 @@ export function setImportToCompileRes(content: string, parseRes: ImportStatement }) return mgcStr.toString() } + +export function transformQuotes(importer: ImportStatement) { + if (!importer.path.startsWith('"') && !importer.path.endsWith('"')) { + if (importer.path.startsWith("'") && importer.path.endsWith("'")) + importer.path = `"${importer.path.slice(1, -1)}"` + else + importer.path = `"${importer.path}"` + } + return importer +} diff --git a/packages/core/parser/__test__/parser-import.spec.ts b/packages/core/parser/__test__/parser-import.spec.ts index 41f81b4..f873cdf 100644 --- a/packages/core/parser/__test__/parser-import.spec.ts +++ b/packages/core/parser/__test__/parser-import.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'vitest' -import { ParserState, parseImports } from '../parser-import-next' +import { ParserState, parseImports } from '../parser-import' describe('parse import', () => { test('parseImports: basic', () => { diff --git a/packages/core/parser/parser-import-next.ts b/packages/core/parser/parser-import-next.ts deleted file mode 100644 index bc50566..0000000 --- a/packages/core/parser/parser-import-next.ts +++ /dev/null @@ -1,225 +0,0 @@ -const innerAtRule = 'media,extend,at-root,debug,warn,forward,mixin,include,function,error' -export enum ParserState { - Initial, - InlineComment, - Comment, - AtStart, - AtEnd, - AtImport, - AtUse, - AtRequire, - QuotesStart, - QuotesEnd, - StringLiteral, -} - -export interface ImportStatement { - type: 'import' | 'use' | 'require' - path: string - start?: number - end?: number - suffix?: string -} -const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\]/g, '') - -export function parseImports(content: string): { - imports: ImportStatement[] - getCurState: () => ParserState - getCurImport: () => undefined | ImportStatement -} { - const imports: ImportStatement[] = [] - let currentImport: ImportStatement | undefined - let state = ParserState.Initial - let i = 0 - let AtPath = '' - const source = delTransformSymbol(content) - while (i < source.length) { - const char = source[i] - switch (state) { - case ParserState.Initial: - if (char === '@') - state = ParserState.AtStart - if (char === '/' && source[i + 1] === '/') - state = ParserState.InlineComment - if (char === '/' && source[i + 1] === '*') - state = ParserState.Comment - break - case ParserState.InlineComment: - if (char === '\n') - state = ParserState.Initial - break - case ParserState.Comment: - if (char === '*' && source[i + 1] === '/') - state = ParserState.Initial - break - case ParserState.AtStart: - if (/[A-Za-z]$/.test(char)) - AtPath = AtPath + char - else - state = ParserState.AtEnd - - if (i === source.length - 1) { - if (char === '"' || char === "'") - throw new Error('syntax error: unmatched quotes') - else - walkContentEnd(i) - } - if (!(/[A-Za-z]$/.test(char)) - && char !== '\n' - && char !== ' ' - && char !== '-') - throw new Error('syntax error') - - break - case ParserState.AtEnd: - if (char !== '\n' && char !== ' ' && char !== '-') { - if (char === '/') - throw new Error('syntax error') - - if (AtPath === 'import') { - AtPath = '' - state = ParserState.AtImport - currentImport = { type: 'import', path: '' } - i-- - } else if (AtPath === 'use') { - AtPath = '' - state = ParserState.AtUse - currentImport = { type: 'use', path: '' } - i-- - } else if (AtPath === 'require') { - AtPath = '' - state = ParserState.AtRequire - currentImport = { type: 'require', path: '' } - i-- - } else { - if (!innerAtRule.includes(AtPath)) - throw new Error('syntax error: unknown At Rule') - - AtPath = '' - state = ParserState.Initial - } - } - - break - case ParserState.AtImport: - case ParserState.AtUse: - case ParserState.AtRequire: - // '@require test.css;@require test2.css' - if (char === '@' && !(/[A-Za-z]$/.test(source[i - 1]))) { - i-- - state = ParserState.Initial - break - } - - if (char === '/' && (source[i + 1] === '/' || source[i + 1] === '*')) { - i-- - state = ParserState.Initial - break - } - - if (char === "'" || char === '"') { - currentImport!.start = i - state = ParserState.QuotesStart - break - } - if (char !== '\n' && char !== ' ') { - currentImport!.start = i - currentImport!.path += char - state = ParserState.StringLiteral - break - } - break - case ParserState.QuotesStart: - if (char === "'" || char === '"') { - currentImport!.end = i - state = ParserState.QuotesEnd - if (i === source.length - 1) - walkContentEnd(i) - break - } - if (i === source.length - 1) - throw new Error('syntax error: unmatched quotes') - - currentImport!.path += char - break - case ParserState.QuotesEnd: - if (i === source.length - 1 || char === ';' || char === '\n') { - walkContentEnd(i) - } else { - i-- - state = ParserState.StringLiteral - } - break - case ParserState.StringLiteral: - if (char === ';' || char === '\n') { - walkContentEnd(i) - break - } - - // '@require test.css@require test2.css' - // '@import ./test1, ./test2' - if ( - char !== '@' - && (char === ' ' - || char === ',' - || char === '"' - || char === "'")) { - const curType = currentImport?.type - walkContentEnd(i) - if (curType === 'import') { - state = ParserState.AtImport - currentImport = { type: 'import', path: '' } - } else if (curType === 'use') { - state = ParserState.AtUse - currentImport = { type: 'use', path: '' } - } else if (curType === 'require') { - state = ParserState.AtRequire - currentImport = { type: 'require', path: '' } - } - - if (char === "'" || char === '"') { - currentImport!.start = i - state = ParserState.QuotesStart - if (i === source.length - 1 && (char === '"' || char === "'")) - throw new Error('syntax error: unmatched quotes') - - break - } - - break - } - - currentImport!.path += char - if (i === source.length - 1) - walkContentEnd(i) - - break - } - i++ - } - - function walkContentEnd(index: number) { - pushCurrentImport(index) - state = ParserState.Initial - } - - function pushCurrentImport(index: number) { - if (currentImport && currentImport.start !== undefined) { - currentImport.end = index - imports.push(currentImport) - currentImport = undefined - } - } - - function getCurState() { - return state - } - function getCurImport() { - return currentImport - } - return { - imports, - getCurState, - getCurImport, - } -} diff --git a/packages/core/parser/parser-import.ts b/packages/core/parser/parser-import.ts index 4b4007d..0232af8 100644 --- a/packages/core/parser/parser-import.ts +++ b/packages/core/parser/parser-import.ts @@ -1,13 +1,18 @@ +const innerAtRule = 'media,extend,at-root,debug,warn,forward,mixin,include,function,error' export enum ParserState { Initial, - At, + InlineComment, + Comment, + AtStart, + AtEnd, AtImport, AtUse, AtRequire, + QuotesStart, + QuotesEnd, StringLiteral, } -// TODO: 解析, 语法(sass) -// TODO: 无引号和分号;语法(sass) + export interface ImportStatement { type: 'import' | 'use' | 'require' path: string @@ -15,7 +20,9 @@ export interface ImportStatement { end?: number suffix?: string } -export function parseImports(source: string): { +const delTransformSymbol = (content: string) => content.replace(/[\r\t\f\v\\]/g, '') + +export function parseImports(content: string, helper?: Array): { imports: ImportStatement[] getCurState: () => ParserState getCurImport: () => undefined | ImportStatement @@ -24,67 +31,191 @@ export function parseImports(source: string): { let currentImport: ImportStatement | undefined let state = ParserState.Initial let i = 0 - + let AtPath = '' + const source = delTransformSymbol(content) while (i < source.length) { const char = source[i] switch (state) { case ParserState.Initial: if (char === '@') - state = ParserState.At - + state = ParserState.AtStart + if (char === '/' && source[i + 1] === '/') + state = ParserState.InlineComment + if (char === '/' && source[i + 1] === '*') + state = ParserState.Comment break - case ParserState.At: - if (char === 'i') { - state = ParserState.AtImport - currentImport = { type: 'import', path: '' } - i++ // skip over "i" to next character - } else if (char === 'u') { - state = ParserState.AtUse - currentImport = { type: 'use', path: '' } - i++ // skip over "u" to next character - } else if (char === 'r') { - state = ParserState.AtRequire - currentImport = { type: 'require', path: '' } - i++ // skip over "u" to next character - } else { + case ParserState.InlineComment: + if (char === '\n') + state = ParserState.Initial + break + case ParserState.Comment: + if (char === '*' && source[i + 1] === '/') state = ParserState.Initial + break + case ParserState.AtStart: + if (/[A-Za-z]$/.test(char)) + AtPath = AtPath + char + else + state = ParserState.AtEnd + + if (i === source.length - 1) { + if (char === '"' || char === "'") + throw new Error('syntax error: unmatched quotes') + else + walkContentEnd(i) + } + if (!(/[A-Za-z]$/.test(char)) + && char !== '\n' + && char !== ' ' + && char !== '-') + throw new Error('syntax error') + + break + case ParserState.AtEnd: + if (char !== '\n' && char !== ' ' && char !== '-') { + if (char === '/') + throw new Error('syntax error') + + if (AtPath === 'import') { + AtPath = '' + state = ParserState.AtImport + currentImport = { type: 'import', path: '' } + i-- + } else if (AtPath === 'use') { + AtPath = '' + state = ParserState.AtUse + currentImport = { type: 'use', path: '' } + i-- + } else if (AtPath === 'require') { + AtPath = '' + state = ParserState.AtRequire + currentImport = { type: 'require', path: '' } + i-- + } else { + if (!innerAtRule.includes(AtPath)) + throw new Error('syntax error: unknown At Rule') + + AtPath = '' + state = ParserState.Initial + } } + break case ParserState.AtImport: case ParserState.AtUse: case ParserState.AtRequire: + // '@require test.css;@require test2.css' + if (char === '@' && !(/[A-Za-z]$/.test(source[i - 1]))) { + i-- + state = ParserState.Initial + break + } + + if (char === '/' && (source[i + 1] === '/' || source[i + 1] === '*')) { + i-- + state = ParserState.Initial + break + } + if (char === "'" || char === '"') { - state = ParserState.StringLiteral + currentImport!.start = i + state = ParserState.QuotesStart + break + } + if (char !== '\n' && char !== ' ') { currentImport!.start = i currentImport!.path += char - } else if (char === ';' || char === '\n') { - if (currentImport && currentImport.start !== undefined) { - currentImport.end = i - imports.push(currentImport) - currentImport = undefined - } - state = ParserState.Initial + state = ParserState.StringLiteral + break } break - case ParserState.StringLiteral: + case ParserState.QuotesStart: if (char === "'" || char === '"') { - if (currentImport!.type === 'import') - state = ParserState.AtImport + currentImport!.end = i + state = ParserState.QuotesEnd + if (i === source.length - 1) + walkContentEnd(i) + break + } + if (i === source.length - 1) + throw new Error('syntax error: unmatched quotes') - if (currentImport!.type === 'use') - state = ParserState.AtUse + currentImport!.path += char + break + case ParserState.QuotesEnd: + if (i === source.length - 1 || char === ';' || char === '\n') { + walkContentEnd(i) + } else { + i-- + state = ParserState.StringLiteral + } + break + case ParserState.StringLiteral: + if (char === ';' || char === '\n') { + walkContentEnd(i) + break + } - if (currentImport!.type === 'require') + // '@require test.css@require test2.css' + // '@import ./test1, ./test2' + if ( + char !== '@' + && (char === ' ' + || char === ',' + || char === '"' + || char === "'")) { + const curType = currentImport?.type + walkContentEnd(i) + if (curType === 'import') { + state = ParserState.AtImport + currentImport = { type: 'import', path: '' } + } else if (curType === 'use') { + state = ParserState.AtUse + currentImport = { type: 'use', path: '' } + } else if (curType === 'require') { state = ParserState.AtRequire + currentImport = { type: 'require', path: '' } + } - currentImport!.path += char - } else { currentImport!.path += char } + if (char === "'" || char === '"') { + currentImport!.start = i + state = ParserState.QuotesStart + if (i === source.length - 1 && (char === '"' || char === "'")) + throw new Error('syntax error: unmatched quotes') + + break + } + + break + } + + currentImport!.path += char + if (i === source.length - 1) + walkContentEnd(i) break } i++ } + function walkContentEnd(index: number) { + pushCurrentImport(index) + state = ParserState.Initial + } + + function pushCurrentImport(index: number) { + if (currentImport && currentImport.start !== undefined) { + currentImport.end = index + if (helper) { + helper.forEach((fn) => { + currentImport = fn(currentImport) + }) + } + imports.push(currentImport) + currentImport = undefined + } + } + function getCurState() { return state } diff --git a/play/src/App.vue b/play/src/App.vue index 9556d3a..088dea1 100644 --- a/play/src/App.vue +++ b/play/src/App.vue @@ -6,6 +6,7 @@ const appAsd = () => 'red' const fooColor = appAsd() const appTheme2 = 'blue' const lessColor = 'greenyellow' +const sassColor = '#94c9ff' const stylColor = '#fd1d7c' const appTheme3 = ref('red') const appTheme4 = reactive({ color: 'red' }) @@ -78,5 +79,5 @@ export default { color: v-bind(color2) }*/ -@import "./assets/sass/foo" +@import ./assets/sass/foo diff --git a/play/src/assets/sass/foo.sass b/play/src/assets/sass/foo.sass index 721b547..7202027 100644 --- a/play/src/assets/sass/foo.sass +++ b/play/src/assets/sass/foo.sass @@ -1,4 +1,4 @@ @import test.sass #app div - color: v-bind(stylColor) + color: v-bind(sassColor) From 6a820a24e0325c7dbfcfb98fc4c5e145b4eeb947 Mon Sep 17 00:00:00 2001 From: baiwusanyu-c <740132583@qq.com> Date: Tue, 28 Mar 2023 11:01:26 +0800 Subject: [PATCH 10/10] feat: support sass indented syntax --- .../__snapshots__/pre-process-css.spec.ts.snap | 9 ++------- .../core/css/__test__/pre-process-css.spec.ts | 13 ------------- packages/core/css/pre-process-css.ts | 11 +---------- packages/core/css/process-css.ts | 14 +++++++------- .../transform/__test__/transform-quotes.spec.ts | 16 ++++++++++++++++ packages/core/transform/transform-quotes.ts | 11 +++++++++++ 6 files changed, 37 insertions(+), 37 deletions(-) create mode 100644 packages/core/transform/__test__/transform-quotes.spec.ts create mode 100644 packages/core/transform/transform-quotes.ts diff --git a/packages/core/css/__test__/__snapshots__/pre-process-css.spec.ts.snap b/packages/core/css/__test__/__snapshots__/pre-process-css.spec.ts.snap index d572319..512b78b 100644 --- a/packages/core/css/__test__/__snapshots__/pre-process-css.spec.ts.snap +++ b/packages/core/css/__test__/__snapshots__/pre-process-css.spec.ts.snap @@ -76,10 +76,7 @@ div{color:v-bind(color)} `; exports[`pre process css > getCurFileContent: basic 1`] = ` -" - - -#app { +"#app { div { color: v-bind(fooColor); } @@ -90,9 +87,7 @@ exports[`pre process css > getCurFileContent: basic 1`] = ` `; exports[`pre process css > getCurFileContent: no ; 1`] = ` -" - -#app { +"#app { div { color: v-bind(fooColor); } diff --git a/packages/core/css/__test__/pre-process-css.spec.ts b/packages/core/css/__test__/pre-process-css.spec.ts index a3563fe..24d07f2 100644 --- a/packages/core/css/__test__/pre-process-css.spec.ts +++ b/packages/core/css/__test__/pre-process-css.spec.ts @@ -9,7 +9,6 @@ import { getCurFileContent, preProcessCSS, setImportToCompileRes, - transformQuotes, walkCSSTree, } from '../pre-process-css' import type { ImportStatement } from '../../parser/parser-import' @@ -612,16 +611,4 @@ describe('pre process css', () => { expect(delTransformSymbol(res)).toBe(delTransformSymbol(mockStylContent)) expect(delTransformSymbol(res)).toMatchSnapshot() }) - - test('transformQuotes', () => { - const testCases = [ - { input: 'hello', expected: '"hello"' }, - { input: '"hello"', expected: '"hello"' }, - { input: "'world'", expected: '"world"' }, - ] - testCases.forEach(({ input, expected }) => { - const result = transformQuotes({ path: input } as any) - expect(result.path).toBe(expected) - }) - }) }) diff --git a/packages/core/css/pre-process-css.ts b/packages/core/css/pre-process-css.ts index b39ca88..0a99a7b 100644 --- a/packages/core/css/pre-process-css.ts +++ b/packages/core/css/pre-process-css.ts @@ -17,6 +17,7 @@ import sass from 'sass' import less from 'less' import stylus from 'stylus' import { parseImports } from '../parser/parser-import' +import { transformQuotes } from '../transform/transform-quotes' import type { ImportStatement } from '../parser/parser-import' import type { ICSSFileMap, SearchGlobOptions } from '../types' @@ -277,13 +278,3 @@ export function setImportToCompileRes(content: string, parseRes: ImportStatement }) return mgcStr.toString() } - -export function transformQuotes(importer: ImportStatement) { - if (!importer.path.startsWith('"') && !importer.path.endsWith('"')) { - if (importer.path.startsWith("'") && importer.path.endsWith("'")) - importer.path = `"${importer.path.slice(1, -1)}"` - else - importer.path = `"${importer.path}"` - } - return importer -} diff --git a/packages/core/css/process-css.ts b/packages/core/css/process-css.ts index d604886..82d5fa6 100644 --- a/packages/core/css/process-css.ts +++ b/packages/core/css/process-css.ts @@ -1,7 +1,6 @@ import path from 'path' -import * as csstree from 'css-tree' import { SUPPORT_FILE, completeSuffix, transformSymbol } from '@unplugin-vue-cssvars/utils' -import { walkCSSTree } from './pre-process-css' +import { parseImports } from '../parser/parser-import' import type { ICSSFile, ICSSFileMap } from '../types' import type { SFCDescriptor } from '@vue/compiler-sfc' @@ -32,10 +31,11 @@ export const createCSSModule = (descriptor: SFCDescriptor, id: string, cssFiles: // 遍历 sfc 的 style 标签内容 for (let i = 0; i < descriptor.styles.length; i++) { const content = descriptor.styles[i].content - const cssAst = csstree.parse(content) - // 根据其 ast,获取 @import 信息 - walkCSSTree(cssAst, (importer) => { - const lang = descriptor.styles[i].lang === SUPPORT_FILE.STYLUS ? SUPPORT_FILE.STYL : descriptor.styles[i].lang + const lang = descriptor.styles[i].lang === SUPPORT_FILE.STYLUS ? SUPPORT_FILE.STYL : descriptor.styles[i].lang + + const parseImporterRes = parseImports(content) + parseImporterRes.imports.forEach((res) => { + const importer = res.path // 添加后缀 // sfc中规则:如果@import 指定了后缀,则根据后缀,否则根据当前 script 标签的 lang 属性(默认css) let key = completeSuffix(transformSymbol(path.resolve(path.parse(id).dir, importer)), lang) @@ -47,7 +47,7 @@ export const createCSSModule = (descriptor: SFCDescriptor, id: string, cssFiles: getCSSFileRecursion(key, cssFiles, (res: ICSSFile) => { importModule.push(res) }) - }, { i: true, v: false }) + }) } return importModule } diff --git a/packages/core/transform/__test__/transform-quotes.spec.ts b/packages/core/transform/__test__/transform-quotes.spec.ts new file mode 100644 index 0000000..c720560 --- /dev/null +++ b/packages/core/transform/__test__/transform-quotes.spec.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from 'vitest' +import { transformQuotes } from '../transform-quotes' + +describe('transform', () => { + test('transformQuotes', () => { + const testCases = [ + { input: 'hello', expected: '"hello"' }, + { input: '"hello"', expected: '"hello"' }, + { input: "'world'", expected: '"world"' }, + ] + testCases.forEach(({ input, expected }) => { + const result = transformQuotes({ path: input } as any) + expect(result.path).toBe(expected) + }) + }) +}) diff --git a/packages/core/transform/transform-quotes.ts b/packages/core/transform/transform-quotes.ts new file mode 100644 index 0000000..dc12119 --- /dev/null +++ b/packages/core/transform/transform-quotes.ts @@ -0,0 +1,11 @@ +import type { ImportStatement } from '../parser/parser-import' + +export function transformQuotes(importer: ImportStatement) { + if (!importer.path.startsWith('"') && !importer.path.endsWith('"')) { + if (importer.path.startsWith("'") && importer.path.endsWith("'")) + importer.path = `"${importer.path.slice(1, -1)}"` + else + importer.path = `"${importer.path}"` + } + return importer +}