feat: introduce vue-to-dsl plugin#1590
feat: introduce vue-to-dsl plugin#1590hexqi merged 65 commits intoopentiny:ospp-2025/source-to-dslfrom
Conversation
WalkthroughAdds a new package packages/vue-to-dsl implementing a Vue SFC → TinyEngine DSL converter (library + CLI), parsers, schema generator, tests, demo app/fixtures, Vite configs, and integrates import/upload UI into the generate-code toolbar with overwrite handling. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant UI as Generate-Code Toolbar
participant C as VueToDslConverter
participant G as Generator
participant S as Page Store
rect rgba(230,247,255,0.6)
note right of UI: Upload (file / directory / zip)
end
U->>UI: Click Import → choose File/Directory/Zip
UI->>C: invoke convertFromFile / convertAppDirectory / convertAppFromZip
C->>C: parse SFC(s) (template, script, style)
C->>G: generateSchema / generateAppSchema
G-->>C: PageSchema / AppSchema
C-->>UI: ConvertResult (schema, errors, warnings, dependencies)
alt Duplicate page names found
UI->>UI: show OverwriteDialog
U->>UI: confirm selection
UI->>S: overwrite existing pages or create new pages
else
UI->>S: create new pages and sync app-level data (i18n, dataSource, globalState)
end
UI->>S: update page tree and navigate to new/updated page
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Areas to focus review on:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 36
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/vue-to-dsl/test/testcases/005_createVM/expected/schema.json (1)
1-1129: Fix serialization anomalies in 005_createVM fixtureThe expected schema for
packages/vue-to-dsl/test/testcases/005_createVM/expected/schema.jsoncontains several serialization issues that must be corrected before re-generating the fixture:
[object Event]artifact in a style prop (line 123)- Duplicate prop casings for labels (
labelPositionvs.label-position, plus repeatedlabel-width) at lines 65–67, 203–205, 542–544, 671–673, 750–752, 911–913- Numeric props emitted as strings rather than numbers:
"active": "2"(line 31)"span": "8"(line 929),"span": "5"(line 941),"span": "7"(line 991),"span": "4"(line 1100)Please update the Vue-to-DSL generator to:
- Properly serialize style values (avoid
[object Event])- Normalize prop naming to a single casing (camelCase or kebab-case per DSL conventions)
- Emit numeric prop values as actual numbers
After making these changes, re-generate the fixture and verify that no other testcases exhibit similar anomalies.
🧹 Nitpick comments (47)
packages/vue-to-dsl/.gitignore (1)
1-1: Consider broadening ignore patterns to avoid committing build/test artifacts.Currently only output/ is ignored. Recommend also ignoring dist/ and any testcase-local outputs to prevent accidental commits of generated files.
Apply this diff:
-output/ +output/ +dist/ +test/**/output/packages/vue-to-dsl/tsconfig.json (1)
15-15: Enable strict mode (or a subset) for better API safety.Given this is a library intended for external consumption, recommend turning on stricter checks (at least noImplicitAny) to catch issues early.
Suggested minimal diff:
- "strict": false, + "strict": true, + "noImplicitOverride": true, + "noUncheckedIndexedAccess": true,packages/vue-to-dsl/vite.config.js (5)
1-1: Add imports for URL-based path resolution (used in alias fix below).Apply this diff to add imports at the top:
-import { defineConfig } from 'vite' +import { defineConfig } from 'vite' +import { fileURLToPath, URL } from 'node:url' +// Optional if you prefer path.resolve instead of URL: +// import { resolve } from 'node:path' +// Optional: if you adopt vite-plugin-dts per tsconfig comment: +// import dts from 'vite-plugin-dts'
12-23: Externalize bothnode:-prefixed and bare Node built-ins.Code may import 'fs' or 'path' without the 'node:' scheme. Add both forms to avoid bundling Node built-ins by accident.
external: [ 'vue', '@vue/compiler-sfc', '@vue/compiler-dom', '@babel/parser', '@babel/traverse', '@babel/types', 'node:fs', 'node:fs/promises', 'node:path', - 'node:url' + 'node:url', + 'fs', + 'path', + 'url' ],
25-33: Globals mapping is unused for ES/CJS outputs.rollupOptions.output.globals only applies to UMD/IIFE. Either remove this block or add 'umd' to formats if a UMD build is desired.
Option A (remove):
- output: { - globals: { - vue: 'Vue', - '@vue/compiler-sfc': 'VueCompilerSFC', - '@vue/compiler-dom': 'VueCompilerDOM', - '@babel/parser': 'BabelParser', - '@babel/traverse': 'BabelTraverse', - '@babel/types': 'BabelTypes' - } - } + // No globals needed for ES/CJSOption B (produce UMD too):
- formats: ['es', 'cjs'], + formats: ['es', 'cjs', 'umd'],Keep the existing globals if you choose UMD.
5-10: Consider ESM filename extension and source maps.Some consumers prefer .mjs for ESM artifacts, and source maps aid debugging.
- fileName: (format) => `tiny-engine-vue-to-dsl.${format === 'es' ? 'js' : format}` + fileName: (format) => `tiny-engine-vue-to-dsl.${format === 'es' ? 'mjs' : 'cjs'}` }, + sourcemap: true,
3-41: Emit .d.ts during the Vite build (matches tsconfig intent).If you don’t already run tsc separately, add vite-plugin-dts so type declarations ship with the library.
Diff within this file:
-import { defineConfig } from 'vite' +import { defineConfig } from 'vite' +import dts from 'vite-plugin-dts' @@ -export default defineConfig({ +export default defineConfig({ + plugins: [ + dts({ + // Generates combined .d.ts in dist and respects tsconfig include/exclude + rollupTypes: true + }) + ],packages/vue-to-dsl/test/testcases/003_options_api/expected/schema.json (1)
14-17: Normalize function string to named function syntaxFor consistency with other fixtures (e.g., 004) and your stated goal to unify arrow → named functions, prefer
function updateMessage()over method shorthand in the serialized function string."methods": { "updateMessage": { "type": "JSFunction", - "value": "updateMessage() { this.message = 'Updated!' }" + "value": "function updateMessage() { this.message = 'Updated!' }" } },packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json (1)
7-8: Stability ofgeneratedAtin testsIf the converter emits dynamic timestamps, assert that tests normalize or ignore
meta.generatedAtto avoid flakiness. If it’s intentionally fixed for fixtures, ignore this note.You can confirm by checking the test harness comparator or snapshot logic.
packages/vue-to-dsl/package.json (2)
12-14: Ensure CLI binary is executable and has a shebang; confirm tsconfig.cli.json outputs ESM/CJS consistentlydist/cli.js must start with a shebang and be executable in published tarball. Also confirm the CLI build target matches "type":"module" (ESM) or switch to .cjs consistently.
If needed, add a postbuild to inject the shebang:
"scripts": { - "build": "pnpm run build:types && vite build && pnpm run build:cli", + "build": "pnpm run build:types && vite build && pnpm run build:cli && node ./scripts/add-shebang.mjs",And create scripts/add-shebang.mjs:
import { readFileSync, writeFileSync } from 'node:fs' const p = 'packages/vue-to-dsl/dist/cli.js' const s = readFileSync(p, 'utf8') if (!s.startsWith('#!')) writeFileSync(p, `#!/usr/bin/env node\n${s}`)Alternatively, set "tsconfig.cli.json" to preserve shebangs.
Also applies to: 18-26
38-45: Double-check dependency classification for runtime vs. build-onlyIf @vue/compiler-sfc and @vue/compiler-dom are only used during conversion at runtime (library/CLI), they belong in dependencies (OK). If they’re only used at build time, move them to devDependencies. Also consider whether depending on "vue" at runtime is necessary for parsing—often the compilers suffice.
Would you like a quick audit script to scan src for runtime imports and propose a move?
packages/vue-to-dsl/README.md (2)
218-233: Fix markdownlint: add language for fenced code block; also align file extensions with TypeScript sourcesThe “项目结构” block misses a language identifier and lists .js while the package builds from TS.
Apply this diff:
-``` +```text src/ -├── converter.js # 主转换器类 -├── parser/ # SFC解析器 -│ └── index.js -├── parsers/ # 各部分解析器 -│ ├── templateParser.js # 模板解析器 -│ ├── scriptParser.js # 脚本解析器 -│ ├── styleParser.js # 样式解析器 -│ └── index.js -├── generator/ # Schema生成器 -│ └── index.js -├── types/ # 类型定义 -│ └── index.js -└── index.js # 主入口文件 +├── converter.ts # 主转换器类 +├── parser/ # SFC解析器 +│ └── index.ts +├── parsers/ # 各部分解析器 +│ ├── templateParser.ts # 模板解析器 +│ ├── scriptParser.ts # 脚本解析器 +│ ├── styleParser.ts # 样式解析器 +│ └── index.ts +├── generator/ # Schema生成器 +│ └── index.ts +├── types/ # 类型定义(如有 JS 运行时代码则保持 .js) +│ └── index.ts +└── index.ts # 主入口文件
29-67: Top-level await in the example may confuse users outside ESM—wrap or note requirementThe sample uses top-level await. Either declare it requires Node ESM (“type”: “module”) or wrap in an async IIFE.
Example alternative:
-const converter = new VueToDslConverter() -// 从字符串转换 -const vueCode = ` +const converter = new VueToDslConverter() +;(async () => { + // 从字符串转换 + const vueCode = ` ...SFC... -const result = await converter.convertFromString(vueCode) -if (result.schema) { - console.log('转换成功:', result.schema) -} else { - console.error('转换失败:', result.errors) -} + const result = await converter.convertFromString(vueCode) + if (result.schema) console.log('转换成功:', result.schema) + else console.error('转换失败:', result.errors) +})()packages/vue-to-dsl/test/testcases/005_createVM/input/component.vue (3)
5-9: Bind numeric prop instead of string literal for activeactive looks numeric; bind it as a number to avoid type coercion.
- active="2" + :active="2"
406-411: Scoped style targeting body will not apply as expectedScoped styles are attribute-scoped to the component root; body won’t match. Use :global or drop scoped for this rule.
-<style scoped> -body { - background-color: #eef0f5; - margin-bottom: 80px; -} -</style> +<style scoped> +:global(body) { + background-color: #eef0f5; + margin-bottom: 80px; +} +</style>
82-90: Repeat of conflicting form props across the fileSame label-position/label-width duplication appears in several tiny-form blocks. Please normalize as in the earlier suggestion to avoid ambiguity.
Also applies to: 196-204, 254-262, 330-337
packages/vue-to-dsl/src/parsers/styleParser.ts (2)
1-6: Type the public API and harden parsing (skip @-rules in parseCSSRules, strip comments, safer matches)Current functions return any and use regexes that can over-match. Type the outputs, ignore @-rules in parseCSSRules (media is handled separately), and strip CSS comments before parsing. Backwards compatible.
-export function parseStyle(style: string, options: any = {}) { +export interface StyleParseOptions { + scoped?: boolean + lang?: string +} + +export interface CSSRule { + selector: string + declarations: Record<string, string> +} + +export interface MediaQueryBlock { + condition: string + content: string + rules: CSSRule[] +} + +export function parseStyle(style: string, options: StyleParseOptions = {}) { if (!style || !style.trim()) { return { css: '', scoped: false, lang: 'css' } } return { css: style.trim(), scoped: options.scoped || false, lang: options.lang || 'css' } } -function parseDeclarations(declarations: string) { +function stripComments(input: string) { + return input.replace(/\/\*[\s\S]*?\*\//g, '') +} + +function parseDeclarations(declarations: string) { const result: Record<string, string> = {} if (!declarations) return result - const declarationList = declarations.split(';').filter((d) => d.trim()) + const declarationList = declarations.split(';').filter((d) => d.trim()) declarationList.forEach((declaration) => { const colonIndex = declaration.indexOf(':') if (colonIndex > 0) { const property = declaration.substring(0, colonIndex).trim() const value = declaration.substring(colonIndex + 1).trim() result[property] = value } }) return result } -export function parseCSSRules(css: string) { - if (!css || !css.trim()) return [] as any[] - const rules: any[] = [] - const ruleMatches = css.match(/([^{}]+)\s*{([^{}]*)}/g) +export function parseCSSRules(css: string): CSSRule[] { + if (!css || !css.trim()) return [] + const rules: CSSRule[] = [] + const cleaned = stripComments(css) + // skip at-rules (@media handled via extractMediaQueries) + const ruleMatches = cleaned.match(/(?:(?!@)[^{}]+)\s*{([^{}]*)}/g) if (ruleMatches) { ruleMatches.forEach((ruleMatch) => { - const match = ruleMatch.match(/([^{}]+)\s*{([^{}]*)}/) + const match = ruleMatch.match(/([^{}]+)\s*{([^{}]*)}/) if (match) { const selector = match[1].trim() const declarations = match[2].trim() rules.push({ selector, declarations: parseDeclarations(declarations) }) } }) } return rules } -export function extractCSSVariables(css: string) { +export function extractCSSVariables(css: string): Record<string, string> { const variables: Record<string, string> = {} if (!css) return variables - const variableMatches = css.match(/--[\w-]+\s*:\s*[^;]+/g) + const variableMatches = stripComments(css).match(/--[\w-]+\s*:\s*[^;]+/g) if (variableMatches) { variableMatches.forEach((match) => { const colonIndex = match.indexOf(':') if (colonIndex > 0) { const name = match.substring(0, colonIndex).trim() const value = match.substring(colonIndex + 1).trim() variables[name] = value } }) } return variables } -export function hasMediaQueries(css: string) { +export function hasMediaQueries(css: string): boolean { if (!css) return false return /@media\s+/.test(css) } -export function extractMediaQueries(css: string) { - const mediaQueries: any[] = [] +export function extractMediaQueries(css: string): MediaQueryBlock[] { + const mediaQueries: MediaQueryBlock[] = [] if (!css) return mediaQueries - const mediaMatches = css.match(/@media[^{]+\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g) + const mediaMatches = stripComments(css).match(/@media[^{]+\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g) if (mediaMatches) { mediaMatches.forEach((match) => { const conditionMatch = match.match(/@media\s+([^{]+)/) if (conditionMatch) { const condition = conditionMatch[1].trim() const content = match.substring(match.indexOf('{') + 1, match.lastIndexOf('}')).trim() mediaQueries.push({ condition, content, rules: parseCSSRules(content) }) } }) } return mediaQueries }Also applies to: 23-38, 40-55, 57-61, 62-77
23-38: Add tests for comment stripping and nested rules inside @mediaCurrent regexes can be tripped by comments and nested content. Please add unit tests covering:
I can scaffold vitest specs under packages/vue-to-dsl/test/styleParser.test.ts if you want.
Also applies to: 62-77
packages/vue-to-dsl/test/testcases/002_complex/expected/schema.json (1)
255-263: Undefined identifiers in expressions: canEdit (missing .value?) and showStats (not declared)
- The button disabled uses "!canEdit". Depending on DSL runtime, ensure this resolves to the computed value. If it needs ".value" or "state.", adjust.
- "condition": "showStats" references an undeclared symbol. Consider "state.showStats" and include showStats in state.
If adopting state.showStats:
- "condition": "showStats", + "condition": "state.showStats",And in state:
"state": { + "showStats": true,Also applies to: 360-363
packages/vue-to-dsl/src/types/index.js (3)
14-19: Tighten typedefs for options to prevent misuse downstreamSuggest making shapes explicit so editors and TS inference are more helpful (especially for
componentMapandcustomParsers).Apply:
- * @typedef {Object} ConvertOptions - * @property {Object} componentMap - 组件映射配置 + * @typedef {Object} ConvertOptions + * @property {Record<string, string>} componentMap - 组件映射配置 * @property {boolean} preserveComments - 是否保留注释 * @property {boolean} strictMode - 是否严格模式 - * @property {Object} customParsers - 自定义解析器 + * @property {{ template?: { parse: Function }, script?: { parse: Function }, style?: { parse: Function } }} customParsers - 自定义解析器
29-42: Clarify collection element types in PageSchema
propsandchildrenas rawArraylose intent; align them with expected shapes (even if loosely).Apply:
- * @property {Array} props - 属性 + * @property {Array<Object>} props - 属性 * @property {string} css - 样式 - * @property {Array} children - 子组件 + * @property {Array<TemplateSchema>} children - 子组件
45-53: Surface special fields in TemplateSchemaSchema defines
keyandrefas top-level, but current typedef leaves them as generic. Make this explicit to guide parsers/generators.Apply:
- * @property {string} key - 唯一键 - * @property {string} ref - 引用 + * @property {string} [key] - 唯一键 + * @property {string} [ref] - 引用packages/vue-to-dsl/test/testcases/002_complex/input/component.vue (2)
73-75: Fetch robustness: handle HTTP errors before parsing JSON
fetchonly throws on network errors. Non-2xx responses should be checked to avoidawait response.json()on error pages.Apply:
- const response = await fetch(`/api/users/${props.userId}`) - const data = await response.json() + const response = await fetch(`/api/users/${props.userId}`) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + const data = await response.json()
99-99: Empty onUnmounted block can be droppedNo cleanup logic; remove for clarity.
Apply:
- onUnmounted(() => {}) + // onUnmounted: 无需清理packages/vue-to-dsl/src/parsers/templateParser.ts (2)
42-50: Hoist special attributeskeyandrefto top-level fields
parseNodePropscurrently places all attributes intoschema.props. Your types define top-levelkeyandref; migrate them out when present.Apply, within
parseTemplateNodeafter parsing props:- if (node.props && node.props.length > 0) schema.props = parseNodeProps(node.props, options) + if (node.props && node.props.length > 0) { + schema.props = parseNodeProps(node.props, options) + if (schema.props.key != null) { + schema.key = String(schema.props.key) + delete schema.props.key + } + if (schema.props.ref != null) { + schema.ref = String(schema.props.ref) + delete schema.props.ref + } + }
6-39: Micro: move HTML tag list to a Set and hoist out of functionSaves allocations per call and gives O(1) lookups.
Apply:
- const htmlTags = [ + const htmlTags = new Set([ 'div', ... - 'main' - ] - if (htmlTags.includes(tag.toLowerCase())) return tag.toLowerCase() + 'main' + ]) + if (htmlTags.has(tag.toLowerCase())) return tag.toLowerCase()And hoist
htmlTagsto module scope.packages/vue-to-dsl/test/testcases/index.test.js (3)
26-26: Directory discovery pattern can false-positive
/\d+_/matches anywhere in the name. If you only want directories starting with digits + underscore (e.g.,001_simple), anchor the regex.Apply:
- const cases = fs.readdirSync(baseDir).filter((name) => /\d+_/.test(name)) + const cases = fs.readdirSync(baseDir).filter((name) => /^\d+_/.test(name))
37-41: Avoid unconditional writes in testsWriting artifacts during tests can pollute worktrees/CI. Gate behind an env var or use
test.onTestFinishedhooks.Apply:
- fs.mkdirSync(path.dirname(outputFile), { recursive: true }) - fs.writeFileSync(outputFile, JSON.stringify(result.schema, null, 2)) + if (process.env.UPDATE_OUTPUT === '1') { + fs.mkdirSync(path.dirname(outputFile), { recursive: true }) + fs.writeFileSync(outputFile, JSON.stringify(result.schema, null, 2)) + }
47-73: Strengthen assertions to catch structural regressionsCurrent checks only assert presence of keys. Consider deep equality (allowing additional keys) to ensure converter fidelity.
Example enhancement:
- Compare
expected.methods[m]andtypeof result.schema.methods[m] === 'object'- Optionally
expect(result.schema).toMatchObject(expected)for flexible shape matching.If you prefer strict conformance for some cases, add a
strict: trueflag in each fixture to opt-in totoStrictEqual.packages/vue-to-dsl/src/index.ts (2)
1-1: Avoid importing .d.ts for side effects; let the TS compiler include types
import './index.d.ts'is unnecessary and can confuse bundlers (there’s no runtime output for .d.ts). Remove it and expose typings via the package.jsontypesfield or by emitting.d.tsfrom build.-import './index.d.ts'
3-7: Public API looks good; consider type-only exports for clarityRe-exports are fine. If you intend consumers to import types, consider explicit type-only exports to avoid accidental value re-exports and improve treeshaking clarity. No blocker.
packages/vue-to-dsl/test/sfc/converter.test.js (2)
4-6: Solid e2e happy-path; add an assertion for generated idsSince the generator assigns 8-char ids to all component nodes (including the root Page), add a quick assertion so we don’t regress id assignment.
expect(result.schema.css).toBeDefined() expect(result.schema.children).toBeDefined() + // id assignment + expect(typeof result.schema.id).toBe('string') + expect(result.schema.id.length).toBe(8) })Also applies to: 34-43
82-104: Add a test for non-statereactive/ref variables to prevent blind spotsCurrent parser primarily extracts
reactivewhen the variable is namedstate. A common pattern isconst count = ref(1). Add a test now; we’ll align the parser shortly (see parser comment).it('should parse <script setup> reactive/computed/lifecycle', async () => { @@ expect(result.schema.methods.inc).toBeDefined() }) + + it('should collect standalone refs in <script setup>', async () => { + const vueCode = ` +<template><div>{{ count }}</div></template> +<script setup> + import { ref } from 'vue' + const count = ref(1) +</script>` + const result = await converter.convertFromString(vueCode) + expect(result.errors).toHaveLength(0) + expect(result.schema.state.count).toBe(1) + })packages/vue-to-dsl/cli.ts (2)
12-13: Import from the package entry (index) to avoid deep path couplingImporting the converter through
./src/converterties the CLI to internal layout. Prefer the public entry (which re-exports the converter) so built artifacts remain stable across refactors.-import { VueToDslConverter } from './src/converter' +import { VueToDslConverter } from './src' // resolved to public index during build
142-156: Export format “js” is ESM-only; consider adding cjs or naming it “esm”If consumers expect CommonJS, this will surprise them. Either add
--format cjsor rename toesmand document. Optional for this PR.packages/vue-to-dsl/src/generator/index.ts (3)
101-115: Consider deterministic ids for repeatable outputs (seed option)
Math.random()ids make diffs noisy. Add an optionalseedin options or adeterministicId(startAt=0)mode for stable conversion in CI. Optional but valuable for fixtures.
108-115: ID assignment only walkschildren; extend to cover common child carriers (e.g., slots)If your template schema nests components under
slots,slotChildren, or similar, ids won’t be assigned there. Consider iterating all object-valued props and arrays that contain nodes withcomponentName.
20-40: Value conversion: support template literals and bigint numerics
convertToPlainValuedoesn’t handle template literals or bigint (e.g., 123n). Not a blocker, but easy to extend if needed.packages/vue-to-dsl/src/parsers/scriptParser.ts (3)
300-334: Options APIdata()is stubbed; parse returned object for initial stateCurrently
databecomes{ data: 'function() { return {} }' }which is not useful downstream. Parse the returned object literal to populateresult.state. This will unlock parity with Setup API.
47-88:getNodeValuereturns the string 'undefined' in some cases; return actualundefinedReturning
'undefined'as a string can leak into schema and cause confusing output. Prefer the realundefined.- return 'undefined' + return undefined @@ - return 'undefined' + return undefined
359-389: Lifecycle detection is good; also consider member calls e.g.,Vue.onMountedYou already support member calls in
isVueReactiveCall; applying a similar pattern for lifecycle (identifier vs member) would improve robustness in atypical codebases.packages/vue-to-dsl/src/converter.ts (4)
49-54: Propagate SFC parser warnings instead of dropping them.
parseSFCcurrently logs warnings to console; these never make it intowarnings. Capture and surface them to callers.try { - const sfcResult = parseSFC(vueCode) + const sfcResult = parseSFC(vueCode) + if ((sfcResult as any).warnings?.length) { + warnings.push(...(sfcResult as any).warnings) + } if (!sfcResult.template && !sfcResult.scriptSetup && !sfcResult.script) { throw new Error('Invalid Vue SFC: no template or script found') }
80-83: Consider filtering out type-only or local self-imports when collecting dependencies.If
parseScriptincludes type-only imports or self-referential paths, bubbling them up may over-report dependencies. Optional enhancement: filter by!imp.isTypeOnlyand exclude relative sources as needed before de-duplication.
128-131: Populateschema.pathderived from filename when absent.Many downstream tools expect a route-like
path. You already injectfileNameandmeta.name; add a defaultpathonce.if (result.schema) { result.schema.fileName = fileName result.schema.meta.name = fileName + if (!result.schema.path) { + result.schema.path = `/${fileName}` + } }
138-154: Speed up multi-file conversion and preserve per-file attribution.Use
Promise.allfor parallelism and attach thefilePathfor easier diagnostics.- async convertMultipleFiles(filePaths: string[]): Promise<ConvertResult[]> { - const results: ConvertResult[] = [] - for (const filePath of filePaths) { - try { - const result = await this.convertFromFile(filePath) - results.push(result) - } catch (error: any) { - results.push({ - schema: null, - dependencies: [], - errors: [`Failed to convert ${filePath}: ${error.message}`], - warnings: [] - }) - } - } - return results - } + async convertMultipleFiles(filePaths: string[]): Promise<ConvertResult[]> { + const tasks = filePaths.map(async (filePath) => { + try { + const result = await this.convertFromFile(filePath) + // Optional: tag result for caller-side attribution + ;(result as any).filePath = filePath + return result + } catch (error: any) { + return { + schema: null, + dependencies: [], + errors: [`Failed to convert ${filePath}: ${error.message}`], + warnings: [], + filePath + } as any + } + }) + return Promise.all(tasks) + }packages/vue-to-dsl/src/parser/index.ts (1)
43-46: Harden file parsing with context-rich errors.Wrap IO in try/catch and rethrow with
filePathcontext so upstream can show actionable errors.-export async function parseVueFile(filePath: string): Promise<any> { - const content = await fs.readFile(filePath, 'utf-8') - return parseSFC(content) -} +export async function parseVueFile(filePath: string): Promise<any> { + try { + const content = await fs.readFile(filePath, 'utf-8') + return parseSFC(content) + } catch (e: any) { + const err = new Error(`Failed to read Vue file "${filePath}": ${e.message}`) + ;(err as any).cause = e + throw err + } +}packages/vue-to-dsl/src/index.d.ts (1)
29-38: Includeidassigned by generator in TemplateSchema.IDs are assigned during generation. Expose them in the type.
export interface TemplateSchema { componentName: string + id?: string props?: Record<string, any> children?: TemplateSchema[] condition?: string loop?: string key?: string ref?: string [key: string]: any }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (28)
packages/vue-to-dsl/.gitignore(1 hunks)packages/vue-to-dsl/README.md(1 hunks)packages/vue-to-dsl/cli.ts(1 hunks)packages/vue-to-dsl/package.json(1 hunks)packages/vue-to-dsl/src/converter.ts(1 hunks)packages/vue-to-dsl/src/generator/index.ts(1 hunks)packages/vue-to-dsl/src/index.d.ts(1 hunks)packages/vue-to-dsl/src/index.ts(1 hunks)packages/vue-to-dsl/src/parser/index.ts(1 hunks)packages/vue-to-dsl/src/parsers/index.ts(1 hunks)packages/vue-to-dsl/src/parsers/scriptParser.ts(1 hunks)packages/vue-to-dsl/src/parsers/styleParser.ts(1 hunks)packages/vue-to-dsl/src/parsers/templateParser.ts(1 hunks)packages/vue-to-dsl/src/types/index.js(1 hunks)packages/vue-to-dsl/test/sfc/converter.test.js(1 hunks)packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/001_simple/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/002_complex/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/002_complex/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/003_options_api/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/003_options_api/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/004_setup_api/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/004_setup_api/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/005_createVM/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/005_createVM/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/index.test.js(1 hunks)packages/vue-to-dsl/tsconfig.json(1 hunks)packages/vue-to-dsl/vite.config.js(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/vue-to-dsl/README.mdpackages/vue-to-dsl/vite.config.jspackages/vue-to-dsl/cli.tspackages/vue-to-dsl/src/index.d.tspackages/vue-to-dsl/package.json
📚 Learning: 2025-01-14T08:42:18.574Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1038
File: packages/plugins/block/index.js:24-24
Timestamp: 2025-01-14T08:42:18.574Z
Learning: In the tiny-engine project, breaking changes are documented in the changelog rather than in JSDoc comments or separate migration guides.
Applied to files:
packages/vue-to-dsl/README.md
📚 Learning: 2024-10-09T01:47:35.507Z
Learnt from: chilingling
PR: opentiny/tiny-engine#583
File: packages/build/vite-config/index.js:1-1
Timestamp: 2024-10-09T01:47:35.507Z
Learning: The `getDefaultConfig` function inside `packages/build/vite-config/src/default-config.js` is intended to remain and be called internally. Ensure no references to this function exist outside of this file.
Applied to files:
packages/vue-to-dsl/vite.config.js
📚 Learning: 2025-01-14T06:40:18.223Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/plugins/page/src/PageGeneral.vue:166-178
Timestamp: 2025-01-14T06:40:18.223Z
Learning: The page structure data in PageGeneral.vue cannot have circular dependencies due to design constraints.
Applied to files:
packages/vue-to-dsl/test/testcases/003_options_api/expected/schema.json
🧬 Code graph analysis (9)
packages/vue-to-dsl/src/parsers/scriptParser.ts (2)
packages/vue-to-dsl/src/index.ts (1)
parseScript(6-6)packages/vue-to-dsl/src/parsers/index.ts (1)
parseScript(2-2)
packages/vue-to-dsl/test/testcases/index.test.js (1)
packages/vue-to-dsl/src/converter.ts (1)
VueToDslConverter(31-163)
packages/vue-to-dsl/src/parsers/styleParser.ts (2)
packages/vue-to-dsl/src/index.ts (1)
parseStyle(6-6)packages/vue-to-dsl/src/parsers/index.ts (5)
parseStyle(3-3)parseCSSRules(3-3)extractCSSVariables(3-3)hasMediaQueries(3-3)extractMediaQueries(3-3)
packages/vue-to-dsl/src/parsers/templateParser.ts (3)
packages/vue-generator/src/generator/vue/sfc/generateAttribute.js (1)
props(596-596)packages/vue-to-dsl/src/index.ts (1)
parseTemplate(6-6)packages/vue-to-dsl/src/parsers/index.ts (1)
parseTemplate(1-1)
packages/vue-to-dsl/src/generator/index.ts (3)
packages/vue-generator/src/generator/vue/sfc/generateAttribute.js (1)
props(596-596)packages/vue-generator/src/templates/vue-template/index.js (1)
value(30-30)packages/vue-to-dsl/src/index.ts (2)
generateSchema(5-5)generateAppSchema(5-5)
packages/vue-to-dsl/test/sfc/converter.test.js (2)
packages/vue-to-dsl/test/testcases/index.test.js (1)
converter(8-24)packages/vue-to-dsl/src/converter.ts (1)
VueToDslConverter(31-163)
packages/vue-to-dsl/cli.ts (1)
packages/vue-to-dsl/src/converter.ts (1)
VueToDslConverter(31-163)
packages/vue-to-dsl/src/converter.ts (5)
packages/vue-to-dsl/src/parser/index.ts (1)
parseSFC(4-41)packages/vue-to-dsl/src/parsers/templateParser.ts (1)
parseTemplate(231-238)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(399-427)packages/vue-to-dsl/src/parsers/styleParser.ts (1)
parseStyle(1-6)packages/vue-to-dsl/src/generator/index.ts (1)
generateSchema(140-165)
packages/vue-to-dsl/src/parser/index.ts (2)
packages/build/vite-plugin-meta-comments/src/transform-sfc.js (1)
parse(50-50)packages/vue-generator/src/utils/vue-sfc-validator.js (1)
errors(26-26)
🪛 markdownlint-cli2 (0.17.2)
packages/vue-to-dsl/README.md
218-218: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 Biome (2.1.2)
packages/vue-to-dsl/src/parsers/templateParser.ts
[error] 133-133: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 133-133: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 133-133: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 133-133: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
🔇 Additional comments (17)
packages/vue-to-dsl/tsconfig.json (2)
5-5: Confirmed: TypeScript ≥ 5.2 in use, somoduleResolution: "Bundler"is supportedBoth the root and the
vue-to-dslpackage declare TS versions well above 5.2, so no changes are needed.• Root
package.json:"typescript": "~5.4.5"
•packages/vue-to-dsl/package.json:"typescript": "^5.4.0"
8-9: Declarations emission verified
Thebuildscript in packages/vue-to-dsl/package.json runstsc -p tsconfig.json(viabuild:types) to emit.d.tsfiles intodist, and then invokesvite buildto produce the JavaScript bundles. Withdeclaration: trueandemitDeclarationOnly: truein tsconfig, you will end up with both JS and type declarations indistas intended—no additional steps are required.Optional refactor:
- To simplify the pipeline, you could add vite-plugin-dts to your Vite configuration and remove the separate
tscinvocation, ensuring declarations are generated during the Vite build.packages/vue-to-dsl/test/testcases/004_setup_api/input/component.vue (1)
11-33: Good, representative script-setup fixture.Covers refs/reactive/computed, function + arrow function (exercises your “arrow → named” normalization), and lifecycle hooks—useful breadth for the converter.
packages/vue-to-dsl/test/testcases/005_createVM/expected/schema.json (3)
31-31: Type fidelity: numeric props serialized as strings."active": "2" is likely numeric. Confirm the DSL spec; if numeric, serialize as number to avoid runtime coercion.
- "active": "2", + "active": 2,
836-839: Loop expression looks correct.Using a JSExpression to iterate over state aligns with the DSL’s dynamic data patterns.
1-20: Meta.generatedAt is static in fixture—ensure tests remain stable.If your tests compare full JSON, a static timestamp is fine. If you switch to dynamic timestamps, exclude or normalize this field during assertions.
packages/vue-to-dsl/test/testcases/003_options_api/input/component.vue (1)
24-32: Mixing Options API with setup(): OKUsing
data()/methods/mountedalongsidesetup()is valid in Vue 3 and is a good fixture to exercise the converter. No issues here.packages/vue-to-dsl/test/testcases/004_setup_api/expected/schema.json (1)
14-47: Rest looks coherentMethods, computed, and lifecycle shapes align with the described converter behavior. IDs and CSS are consistent with other fixtures.
packages/vue-to-dsl/test/testcases/001_simple/input/component.vue (1)
1-8: Template and styles: LGTMThe structure and scoped styles are clear and good for exercising basic bindings and events.
Also applies to: 42-61
packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json (1)
14-25: Align expected method bodies withref-basedcountIf you adopt the
reffix in the input, update the schema’s method bodies to referencecount.valuerather thanstate.count.value."methods": { "increment": { "type": "JSFunction", - "value": "function increment() { state.count.value++ }" + "value": "function increment() { count.value++ }" }, "decrement": { "type": "JSFunction", - "value": "function decrement() { state.count.value-- }" + "value": "function decrement() { count.value-- }" }, "reset": { "type": "JSFunction", - "value": "function reset() { state.count.value = 0 }" + "value": "function reset() { count.value = 0 }" } },packages/vue-to-dsl/src/types/index.js (1)
73-73: Module boundary looks goodUsing
export {}to force module scope is correct and avoids global JSDoc bleed.packages/vue-to-dsl/src/parsers/templateParser.ts (1)
151-165: v-on without argument is not handled (object syntax)
v-on="listenersObject"is valid but this branch assumes a single event name, defaulting to 'click'. Consider supporting the object form or fail clearly.I can extend this to detect
prop.arg === nulland attempt literal/object parsing, mapping each key to a JSExpression. Confirm if your DSL expects spread-like semantics or materialized props per event.packages/vue-to-dsl/src/parsers/index.ts (1)
1-3: Public surface looks coherentRe-exporting the three parsers (and style utilities) from one place is clean and keeps the main entry lightweight.
packages/vue-to-dsl/cli.ts (1)
158-166: Ensure output directory creation covers absolute paths and Windows drive rootsCurrent logic is fine; just a note that
path.dirname('C:\\a.js')yields'C:\\'which is truthy, so mkdir is attempted (OK). No action.packages/vue-to-dsl/src/converter.ts (1)
156-162: Options mutator/getter look good.packages/vue-to-dsl/src/parser/index.ts (1)
48-50: Validation helper is concise and correct.packages/vue-to-dsl/src/index.d.ts (1)
1-154: No circular self-imports in source files; only in README.md examplesThe import scan under
packages/vue-to-dsl/found no instances ofimport … from '@opentiny/tiny-engine-vue-to-dsl'in any source files—only in the documentation example at:
- packages/vue-to-dsl/README.md:29
Since this is purely a usage example and not part of the package’s implementation, it poses no risk of circular dependency. No code changes are required.
packages/vue-to-dsl/cli.ts
Outdated
| for (let i = 1; i < args.length; i += 2) { | ||
| const option = args[i] | ||
| const value = args[i + 1] | ||
|
|
||
| switch (option) { | ||
| case '--output': | ||
| case '-o': | ||
| outputFile = value | ||
| break | ||
| case '--format': | ||
| case '-f': | ||
| if (value === 'json' || value === 'js') { | ||
| format = value | ||
| } | ||
| break | ||
| case '--help': | ||
| case '-h': | ||
| console.log('显示帮助信息...') | ||
| process.exit(0) | ||
| break | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Make option parsing robust (handle flags without values and mixed order)
The current “step by 2” iteration breaks for flags at the end with missing values and ignores mixed ordering. A small parser tweak keeps it dependency-free while resilient.
-for (let i = 1; i < args.length; i += 2) {
- const option = args[i]
- const value = args[i + 1]
-
- switch (option) {
+for (let i = 1; i < args.length; i++) {
+ const option = args[i]
+ const next = args[i + 1]
+ const hasValue = next && !next.startsWith('-')
+ switch (option) {
case '--output':
case '-o':
- outputFile = value
- break
+ if (!hasValue) {
+ console.error('❌ 错误: --output/-o 需要一个文件路径')
+ process.exit(1)
+ }
+ outputFile = next
+ i++
+ break
case '--format':
case '-f':
- if (value === 'json' || value === 'js') {
- format = value
- }
- break
+ if (!hasValue) {
+ console.error('❌ 错误: --format/-f 需要一个值: json | js')
+ process.exit(1)
+ }
+ if (next === 'json' || next === 'js') {
+ format = next as any
+ } else {
+ console.error(`❌ 错误: 不支持的输出格式 - ${next}`)
+ process.exit(1)
+ }
+ i++
+ break
case '--help':
case '-h':
console.log('显示帮助信息...')
process.exit(0)
break
+ default:
+ if (option.startsWith('-')) {
+ console.warn(`⚠️ 未知参数: ${option}(已忽略)`)
+ }
}
}Run the CLI locally with edge cases:
- With trailing “-o” and no value (should error).
- Mixed order: “file.vue -f js -o out.js”.
- Unknown flag: “--foo” (should warn).
🏁 Script executed:
#!/bin/bash
# Locate the CLI file
CLI_FILE=$(fd cli.ts)
echo "Found CLI file at: $CLI_FILE"
# Print the first 100 lines to review the argument parsing logic
sed -n '1,100p' "$CLI_FILE"Length of output: 2740
Improve CLI argument parsing robustness
The current loop in packages/vue-to-dsl/cli.ts (lines 44–66) steps by two and assumes every flag has a following value. This silently misbehaves when a flag is trailing with no value, accepts invalid formats without error, and ignores unknown flags entirely. We should:
- Iterate over
argsone-by-one, detect whether the next token is a value (non-flag) before consuming it - Emit errors and
process.exit(1)for missing or invalid values on--output/-oand--format/-f - Warn (but continue) on any unknown flags
- Increment the index when a flag’s value is consumed
Proposed diff at lines 44–66 in packages/vue-to-dsl/cli.ts:
-for (let i = 1; i < args.length; i += 2) {
- const option = args[i]
- const value = args[i + 1]
-
- switch (option) {
+for (let i = 1; i < args.length; i++) {
+ const option = args[i]
+ const next = args[i + 1]
+ // value exists and isn’t another flag
+ const hasValue = next !== undefined && !next.startsWith('-')
+ switch (option) {
case '--output':
case '-o':
- outputFile = value
- break
+ if (!hasValue) {
+ console.error('❌ 错误: --output/-o 需要一个文件路径')
+ process.exit(1)
+ }
+ outputFile = next
+ i++
+ break
case '--format':
case '-f':
- if (value === 'json' || value === 'js') {
- format = value
- }
- break
+ if (!hasValue) {
+ console.error('❌ 错误: --format/-f 需要一个值: json | js')
+ process.exit(1)
+ }
+ if (next === 'json' || next === 'js') {
+ format = next
+ } else {
+ console.error(`❌ 错误: 不支持的输出格式 - ${next}`)
+ process.exit(1)
+ }
+ i++
+ break
case '--help':
case '-h':
console.log('显示帮助信息...')
process.exit(0)
break
+ default:
+ if (option.startsWith('-')) {
+ console.warn(`⚠️ 未知参数: ${option}(已忽略)`)
+ }
}
}Run these edge-case checks after applying:
- Trailing flag without value:
node cli.ts file.vue -o(should exit with error) - Mixed ordering:
node cli.ts file.vue -f js -o out.js(both flags applied) - Unknown flag:
node cli.ts file.vue --foo(warns, continues)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (let i = 1; i < args.length; i += 2) { | |
| const option = args[i] | |
| const value = args[i + 1] | |
| switch (option) { | |
| case '--output': | |
| case '-o': | |
| outputFile = value | |
| break | |
| case '--format': | |
| case '-f': | |
| if (value === 'json' || value === 'js') { | |
| format = value | |
| } | |
| break | |
| case '--help': | |
| case '-h': | |
| console.log('显示帮助信息...') | |
| process.exit(0) | |
| break | |
| } | |
| } | |
| for (let i = 1; i < args.length; i++) { | |
| const option = args[i] | |
| const next = args[i + 1] | |
| // value exists and isn’t another flag | |
| const hasValue = next !== undefined && !next.startsWith('-') | |
| switch (option) { | |
| case '--output': | |
| case '-o': | |
| if (!hasValue) { | |
| console.error('❌ 错误: --output/-o 需要一个文件路径') | |
| process.exit(1) | |
| } | |
| outputFile = next | |
| i++ | |
| break | |
| case '--format': | |
| case '-f': | |
| if (!hasValue) { | |
| console.error('❌ 错误: --format/-f 需要一个值: json | js') | |
| process.exit(1) | |
| } | |
| if (next === 'json' || next === 'js') { | |
| format = next | |
| } else { | |
| console.error(`❌ 错误: 不支持的输出格式 - ${next}`) | |
| process.exit(1) | |
| } | |
| i++ | |
| break | |
| case '--help': | |
| case '-h': | |
| console.log('显示帮助信息...') | |
| process.exit(0) | |
| break | |
| default: | |
| if (option.startsWith('-')) { | |
| console.warn(`⚠️ 未知参数: ${option}(已忽略)`) | |
| } | |
| } | |
| } |
| "type": "module", | ||
| "main": "dist/tiny-engine-vue-to-dsl.cjs", | ||
| "module": "dist/tiny-engine-vue-to-dsl.js", | ||
| "types": "dist/index.d.ts", | ||
| "bin": { | ||
| "tiny-vue-to-dsl": "dist/cli.js" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add exports map, engines, and sideEffects for correct dual-publish and better DX
Define explicit ESM/CJS entrypoints via exports, declare Node engine (Vite 5 requires Node 18+), and mark sideEffects to help tree-shaking.
Apply this diff:
"type": "module",
+ "engines": {
+ "node": ">=18.17"
+ },
"main": "dist/tiny-engine-vue-to-dsl.cjs",
"module": "dist/tiny-engine-vue-to-dsl.js",
"types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/tiny-engine-vue-to-dsl.js",
+ "require": "./dist/tiny-engine-vue-to-dsl.cjs",
+ "default": "./dist/tiny-engine-vue-to-dsl.js"
+ },
+ "./cli": "./dist/cli.js"
+ },
"bin": {
"tiny-vue-to-dsl": "dist/cli.js"
},
+ "sideEffects": false,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "type": "module", | |
| "main": "dist/tiny-engine-vue-to-dsl.cjs", | |
| "module": "dist/tiny-engine-vue-to-dsl.js", | |
| "types": "dist/index.d.ts", | |
| "bin": { | |
| "tiny-vue-to-dsl": "dist/cli.js" | |
| }, | |
| "files": [ | |
| "dist" | |
| ], | |
| "type": "module", | |
| "engines": { | |
| "node": ">=18.17" | |
| }, | |
| "main": "dist/tiny-engine-vue-to-dsl.cjs", | |
| "module": "dist/tiny-engine-vue-to-dsl.js", | |
| "types": "dist/index.d.ts", | |
| "exports": { | |
| ".": { | |
| "types": "./dist/index.d.ts", | |
| "import": "./dist/tiny-engine-vue-to-dsl.js", | |
| "require": "./dist/tiny-engine-vue-to-dsl.cjs", | |
| "default": "./dist/tiny-engine-vue-to-dsl.js" | |
| }, | |
| "./cli": "./dist/cli.js" | |
| }, | |
| "bin": { | |
| "tiny-vue-to-dsl": "dist/cli.js" | |
| }, | |
| "sideEffects": false, | |
| "files": [ | |
| "dist" | |
| ], |
| templateSchema = this.options.customParsers?.template | ||
| ? this.options.customParsers.template.parse(sfcResult.template) | ||
| : parseTemplate(sfcResult.template, this.options as any) | ||
| } catch (error: any) { | ||
| errors.push(`Template parsing error: ${error.message}`) | ||
| if (this.options.strictMode) throw error | ||
| } |
There was a problem hiding this comment.
Custom template parser is called without options and may return wrong shape.
Public API implies custom parsers accept (code, options) and template parse returns an array. Pass options and normalize to an array. Otherwise, downstream generateSchema may receive a non-array.
- templateSchema = this.options.customParsers?.template
- ? this.options.customParsers.template.parse(sfcResult.template)
+ templateSchema = this.options.customParsers?.template
+ ? this.options.customParsers.template.parse(sfcResult.template, this.options)
: parseTemplate(sfcResult.template, this.options as any)
+ if (!Array.isArray(templateSchema)) {
+ templateSchema = templateSchema ? [templateSchema] : []
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| templateSchema = this.options.customParsers?.template | |
| ? this.options.customParsers.template.parse(sfcResult.template) | |
| : parseTemplate(sfcResult.template, this.options as any) | |
| } catch (error: any) { | |
| errors.push(`Template parsing error: ${error.message}`) | |
| if (this.options.strictMode) throw error | |
| } | |
| templateSchema = this.options.customParsers?.template | |
| ? this.options.customParsers.template.parse(sfcResult.template, this.options) | |
| : parseTemplate(sfcResult.template, this.options as any) | |
| if (!Array.isArray(templateSchema)) { | |
| templateSchema = templateSchema ? [templateSchema] : [] | |
| } |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/src/converter.ts around lines 61 to 67, the custom
template parser is invoked without passing options and its return value isn't
normalized to an array; update the call to pass this.options (e.g.,
this.options.customParsers.template.parse(sfcResult.template, this.options)) and
after parsing ensure templateSchema is an array (if result is not an array, wrap
it: templateSchema = Array.isArray(result) ? result : [result]); keep the
existing fallback to parseTemplate(sfcResult.template, this.options) and
preserve the existing error push/strictMode throw behavior.
| scriptSchema = this.options.customParsers?.script | ||
| ? this.options.customParsers.script.parse(scriptContent) | ||
| : parseScript(scriptContent, { | ||
| isSetup: !!sfcResult.scriptSetup, | ||
| ...(this.options as any) | ||
| }) | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Custom script parser is called without options.
Align with default parser call and the declared API by passing options (including isSetup).
- scriptSchema = this.options.customParsers?.script
- ? this.options.customParsers.script.parse(scriptContent)
+ scriptSchema = this.options.customParsers?.script
+ ? this.options.customParsers.script.parse(scriptContent, {
+ isSetup: !!sfcResult.scriptSetup,
+ ...(this.options as any)
+ })
: parseScript(scriptContent, {
isSetup: !!sfcResult.scriptSetup,
...(this.options as any)
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| scriptSchema = this.options.customParsers?.script | |
| ? this.options.customParsers.script.parse(scriptContent) | |
| : parseScript(scriptContent, { | |
| isSetup: !!sfcResult.scriptSetup, | |
| ...(this.options as any) | |
| }) | |
| scriptSchema = this.options.customParsers?.script | |
| ? this.options.customParsers.script.parse(scriptContent, { | |
| isSetup: !!sfcResult.scriptSetup, | |
| ...(this.options as any) | |
| }) | |
| : parseScript(scriptContent, { | |
| isSetup: !!sfcResult.scriptSetup, | |
| ...(this.options as any) | |
| }) |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/src/converter.ts around lines 73 to 79, the custom script
parser is being invoked with only the script content and no options; update the
call to pass the same options object used for the default parser (include
isSetup: !!sfcResult.scriptSetup and spread this.options) so the custom parser
receives the same configuration shape as parseScript.
| "componentName": "span", | ||
| "props": { | ||
| "style": "background-color: [object Event]; color: #8a8e99; font-size: 12px" | ||
| }, | ||
| "children": [ |
There was a problem hiding this comment.
Data corruption: style contains '[object Event]'.
"background-color: [object Event]" indicates a non-serializable value leaked into style serialization. This is a converter bug and will propagate invalid CSS into the DSL.
Short-term fix in expected schema (to prevent masking the issue, prefer fixing the generator and updating all affected fixtures):
- "style": "background-color: [object Event]; color: #8a8e99; font-size: 12px"
+ "style": "color: #8a8e99; font-size: 12px"Follow-up in generator (pseudo):
- When building inline style strings, only accept primitive values (string/number). Skip others.
- Consider normalizing CSS values and trimming trailing semicolons.
Example guard:
for (const [k, v] of Object.entries(styleObj)) {
if (typeof v !== 'string' && typeof v !== 'number') continue;
parts.push(`${toKebab(k)}: ${String(v)}`);
}🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/testcases/005_createVM/expected/schema.json around
lines 121-125 the inline style value contains "background-color: [object Event]"
which indicates a non-primitive value leaked into style serialization; update
the generator that builds inline style strings to only include primitive values
(strings or numbers) and skip objects, functions, events or other
non-serializable types, normalize property names to kebab-case, trim extraneous
semicolons/spaces, and then regenerate/update the expected fixture(s) so the
schema no longer contains "[object Event]".
| <tiny-form | ||
| labelWidth="80px" | ||
| labelPosition="top" | ||
| :inline="false" | ||
| label-position="left " | ||
| label-width="150px" | ||
| style="border-radius: 0px" | ||
| > |
There was a problem hiding this comment.
Duplicate and conflicting form props: labelWidth/label-width, labelPosition/label-position
Both camelCase and kebab-case props are set with different values; Vue normalizes them to the same prop so the last one wins unpredictably. Remove duplicates and trailing space in values.
Apply this diff:
- <tiny-form
- labelWidth="80px"
- labelPosition="top"
- :inline="false"
- label-position="left "
- label-width="150px"
- style="border-radius: 0px"
- >
+ <tiny-form
+ label-position="left"
+ label-width="150px"
+ :inline="false"
+ style="border-radius: 0px"
+ >Repeat the same clean-up for other tiny-form usages in this file.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <tiny-form | |
| labelWidth="80px" | |
| labelPosition="top" | |
| :inline="false" | |
| label-position="left " | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > | |
| <tiny-form | |
| label-position="left" | |
| label-width="150px" | |
| :inline="false" | |
| style="border-radius: 0px" | |
| > |
🤖 Prompt for AI Agents
packages/vue-to-dsl/test/testcases/005_createVM/input/component.vue lines 26-33:
the tiny-form declares duplicate/conflicting props (labelWidth vs label-width
and labelPosition vs label-position) and has a trailing space in "left "; remove
the duplicates so each prop is declared only once (use one naming style
consistently — e.g., keep label-width="150px" and label-position="left"), delete
the other conflicting attributes (e.g., remove labelWidth="80px" and
labelPosition="top"), trim any trailing spaces in prop values, and apply the
same de-duplication and trimming to every other tiny-form usage in this file.
| <span style="background-color: [object Event]; color: #8a8e99; font-size: 12px" | ||
| >温馨提示:页面左上角切换区域</span | ||
| > |
There was a problem hiding this comment.
Invalid CSS in inline style: background-color: [object Event]
This is a runtime artifact string and will render invalid CSS. Replace with a valid color token.
- <span style="background-color: [object Event]; color: #8a8e99; font-size: 12px"
+ <span style="background-color: transparent; color: #8a8e99; font-size: 12px"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span style="background-color: [object Event]; color: #8a8e99; font-size: 12px" | |
| >温馨提示:页面左上角切换区域</span | |
| > | |
| <span style="background-color: transparent; color: #8a8e99; font-size: 12px" | |
| >温馨提示:页面左上角切换区域</span | |
| > |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/testcases/005_createVM/input/component.vue around
lines 49 to 51, the inline style contains an invalid value "background-color:
[object Event]" which will produce invalid CSS at runtime; replace that runtime
artifact with a valid color token or value (for example a CSS variable like
var(--color-warning) or a hex/RGB value) or remove the inline background-color
and apply a class instead so the rendered CSS is valid.
| <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex"> | ||
| <tiny-icon-panel-mini | ||
| style="margin-right: 10px; width: 16px; height: 16px" | ||
| fill="currentColor" | ||
| ></tiny-icon-panel-mini> | ||
| <tiny-select | ||
| modelValue="" | ||
| placeholder="请选择" | ||
| style="width: 200px; margin-right: 10px" | ||
| :options="[ | ||
| { value: '1', label: '黄金糕' }, | ||
| { value: '2', label: '双皮奶' } | ||
| ]" | ||
| ></tiny-select> | ||
| <tiny-input placeholder="请输入" modelValue="" style="width: 120px; margin-right: 10px"></tiny-input> | ||
| <span style="color: #575d6c; font-size: 12px; margin-right: 10px">GiB IOPS上限600,IOPS突发上限5,000</span> | ||
| <tiny-input placeholder="请输入" modelValue="" style="width: 120px"></tiny-input> | ||
| </div> |
There was a problem hiding this comment.
Invalid v-for syntax: empty tuple “() in …”
v-for requires an item identifier. Use item or (item, index).
- <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex">
+ <div v-for="(_, idx) in state.dataDisk" :key="idx" style="margin-top: 12px; display: flex">Note: add a key for list rendering stability.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex"> | |
| <tiny-icon-panel-mini | |
| style="margin-right: 10px; width: 16px; height: 16px" | |
| fill="currentColor" | |
| ></tiny-icon-panel-mini> | |
| <tiny-select | |
| modelValue="" | |
| placeholder="请选择" | |
| style="width: 200px; margin-right: 10px" | |
| :options="[ | |
| { value: '1', label: '黄金糕' }, | |
| { value: '2', label: '双皮奶' } | |
| ]" | |
| ></tiny-select> | |
| <tiny-input placeholder="请输入" modelValue="" style="width: 120px; margin-right: 10px"></tiny-input> | |
| <span style="color: #575d6c; font-size: 12px; margin-right: 10px">GiB IOPS上限600,IOPS突发上限5,000</span> | |
| <tiny-input placeholder="请输入" modelValue="" style="width: 120px"></tiny-input> | |
| </div> | |
| <div v-for="(_, idx) in state.dataDisk" :key="idx" style="margin-top: 12px; display: flex"> | |
| <tiny-icon-panel-mini | |
| style="margin-right: 10px; width: 16px; height: 16px" | |
| fill="currentColor" | |
| ></tiny-icon-panel-mini> | |
| <tiny-select | |
| modelValue="" | |
| placeholder="请选择" | |
| style="width: 200px; margin-right: 10px" | |
| :options="[ | |
| { value: '1', label: '黄金糕' }, | |
| { value: '2', label: '双皮奶' } | |
| ]" | |
| ></tiny-select> | |
| <tiny-input placeholder="请输入" modelValue="" style="width: 120px; margin-right: 10px"></tiny-input> | |
| <span style="color: #575d6c; font-size: 12px; margin-right: 10px">GiB IOPS上限600,IOPS突发上限5,000</span> | |
| <tiny-input placeholder="请输入" modelValue="" style="width: 120px"></tiny-input> | |
| </div> |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/testcases/005_createVM/input/component.vue around
lines 287 to 304, the v-for uses an empty tuple "() in state.dataDisk" which is
invalid; replace it with a proper item identifier such as "item in
state.dataDisk" or "(item, index) in state.dataDisk" and add a unique key
binding (e.g. :key="index" or :key="item.id") on the root element inside the
loop for stable list rendering.
| alias: { | ||
| '@': '/src' | ||
| } |
There was a problem hiding this comment.
Alias path is incorrect; '/src' is not a file-system path.
In Vite, alias targets should be absolute FS paths. Using '/src' will resolve to the OS root, breaking resolution in CI and on Windows.
Apply this diff:
resolve: {
alias: {
- '@': '/src'
+ '@': fileURLToPath(new URL('./src', import.meta.url))
}
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
packages/vue-to-dsl/vite.config.js lines 37-39: the alias target '/src' is an
absolute OS root path and will break resolution on CI/Windows; change the alias
target to an absolute filesystem path by resolving the project src directory
(e.g. use path.resolve(__dirname, 'src') or import/require path and call
path.resolve with __dirname and 'src'), and add the corresponding require/import
of the path module at the top of the file.
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/build/vite-config/src/vite-plugins/devAliasPlugin.js (1)
9-12: Boolean useSourceAlias will throw when accessing basePath — add type guardWhen useSourceAlias is true (per JSDoc it's boolean|Object), accessing useSourceAlias.basePath throws.
Apply:
- const defaultBasePath = path.resolve(process.cwd(), '..') // parent path - const basePath = useSourceAlias.basePath || defaultBasePath + const defaultBasePath = path.resolve(process.cwd(), '..') // parent path + const basePath = + typeof useSourceAlias === 'object' && useSourceAlias && useSourceAlias.basePath + ? useSourceAlias.basePath + : defaultBasePath
♻️ Duplicate comments (4)
packages/vue-to-dsl/src/converter.ts (4)
97-106: Pass style options (lang/scoped) and options to custom style parser.- styleSchema = this.options.customParsers?.style - ? this.options.customParsers.style.parse(sfcResult.style) - : parseStyle(sfcResult.style, this.options as any) + const styleOpts = + (sfcResult as any).styleBlocks?.length + ? { + lang: (sfcResult as any).styleBlocks[0].lang, + scoped: (sfcResult as any).styleBlocks.some((b: any) => b.scoped) + } + : {} + styleSchema = this.options.customParsers?.style + ? this.options.customParsers.style.parse(sfcResult.style, { ...(this.options as any), ...styleOpts }) + : parseStyle(sfcResult.style, { ...(this.options as any), ...styleOpts })
14-18: Update custom parser typings to accept options.customParsers?: { - template?: { parse: (code: string) => any } - script?: { parse: (code: string) => any } - style?: { parse: (code: string) => any } + template?: { parse: (code: string, options?: any) => any } + script?: { parse: (code: string, options?: any) => any } + style?: { parse: (code: string, options?: any) => any } }
60-69: Pass options to custom template parser and normalize to array.- templateSchema = this.options.customParsers?.template - ? this.options.customParsers.template.parse(sfcResult.template) - : parseTemplate(sfcResult.template, this.options as any) + const tpl = this.options.customParsers?.template + ? this.options.customParsers.template.parse(sfcResult.template, this.options as any) + : parseTemplate(sfcResult.template, this.options as any) + templateSchema = Array.isArray(tpl) ? tpl : tpl ? [tpl] : []
71-80: Pass options (incl. isSetup) to custom script parser.- scriptSchema = this.options.customParsers?.script - ? this.options.customParsers.script.parse(scriptContent) + scriptSchema = this.options.customParsers?.script + ? this.options.customParsers.script.parse(scriptContent, { + isSetup: !!sfcResult.scriptSetup, + ...(this.options as any) + }) : parseScript(scriptContent, { isSetup: !!sfcResult.scriptSetup, ...(this.options as any) })
🧹 Nitpick comments (13)
packages/toolbars/generate-code/package.json (1)
30-31: Lazy-load VueToDslConverter in Main.vue
Replace the static import at packages/toolbars/generate-code/src/Main.vue:44 with a dynamic import inside the Upload handler (e.g.const { VueToDslConverter } = await import('@opentiny/tiny-engine-vue-to-dsl')) to keep the initial bundle lean.packages/toolbars/generate-code/meta.js (1)
7-8: Preserve backward-compat by keepingicon.default.If any consumer still reads
icon.default, this avoids a silent regression while addingdownload/upload.icon: { - download: 'flow-download', - upload: 'upload' + default: 'flow-download', + download: 'flow-download', + upload: 'upload' },packages/vue-to-dsl/test/full/input/DemoPage.vue (3)
4-4: Use a boolean for Switch’s modelValue to better reflect expected API.String '' may coerce oddly; a boolean is clearer for fixtures.
- <tiny-switch modelValue=""></tiny-switch> + <tiny-switch :modelValue="false"></tiny-switch>
10-13: Deduplicate Vue imports.Use named imports only (and drop the unused TinySwitch import) to keep the fixture minimal.
-import { Switch as TinySwitch } from '@opentiny/vue' -import * as vue from 'vue' -import { defineProps, defineEmits } from 'vue' +import { inject, reactive, defineProps, defineEmits } from 'vue'Then update usages:
-const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode() +const { t, lowcodeWrap, stores } = inject(I18nInjectionKey).lowcode() -const state = vue.reactive({}) +const state = reactive({})
18-21: Optional: guard injection for standalone runs.If the test harness doesn’t provide
I18nInjectionKey,inject(...)returns undefined and this will throw. Add a small guard if you expect isolated execution.packages/vue-to-dsl/test/full/input/testCanvasRowCol.vue (2)
31-34: Deduplicate Vue imports.Prefer named imports over
* as vuefor readability in fixtures.-import * as vue from 'vue' -import { defineProps, defineEmits } from 'vue' +import { inject, reactive, defineProps, defineEmits } from 'vue'And update below:
-const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode() +const { t, lowcodeWrap, stores } = inject(I18nInjectionKey).lowcode() -const state = vue.reactive({ +const state = reactive({
55-60: Scoped style won’t affectbody.With
scoped, selectors are attribute-scoped;bodywon’t match. Use global style or :global().-<style scoped> +<style> body { background-color: #eef0f5; margin-bottom: 80px; } </style>packages/vue-to-dsl/test/full/input/createVm.vue (1)
411-415: Unsafe injection access may throw when i18n is not provided.Guard inject() to avoid runtime errors outside lowcode runtime.
-const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode() +const injected = vue.inject(I18nInjectionKey) as any +const { t, lowcodeWrap, stores } = injected?.lowcode?.() || { t: (s: any) => s, lowcodeWrap: (_: any) => (_: any) => ({}), stores: {} }packages/vue-to-dsl/test/full/index.test.js (3)
18-20: Test name mismatches behavior.It converts all .vue files, not “page1.vue and page2.vue”.
- it('should convert page1.vue and page2.vue and write outputs', async () => { + it('should convert all *.vue inputs and write outputs', async () => {
21-23: Debug logs in tests.Consider removing or gating with env to keep CI output clean.
- console.log('Input directory files:', inputFiles) + // console.debug('Input directory files:', inputFiles)
31-33: Assert result count equals input count.Quick sanity check to catch silent skips.
const results = await converter.convertMultipleFiles(inputFilePathList) + expect(results.length).toBe(inputFilePathList.length)packages/toolbars/generate-code/src/Main.vue (1)
277-281: Allow reusing the same useCanvas refs; avoid repeated calls.Cache once in setup to prevent creating new instances in handlers.
- const triggerUpload = () => { + const triggerUpload = () => { fileInputRef.value?.click() }Outside handlers:
- const { isBlock, getCurrentPage } = useCanvas() + const { isBlock, getCurrentPage, pageState, resetBlockCanvasState, resetPageCanvasState } = useCanvas()packages/vue-to-dsl/src/converter.ts (1)
121-124: Preserve partial results on failure.Consider returning collected dependencies/warnings even when a conversion error occurs.
- } catch (error: any) { - errors.push(`Conversion error: ${error.message}`) - return { schema: null, dependencies: [], errors, warnings } + } catch (error: any) { + errors.push(`Conversion error: ${error.message}`) + return { schema: null, dependencies: [...new Set(dependencies)], errors, warnings }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
packages/build/vite-config/src/vite-plugins/devAliasPlugin.js(1 hunks)packages/toolbars/generate-code/meta.js(1 hunks)packages/toolbars/generate-code/package.json(1 hunks)packages/toolbars/generate-code/src/Main.vue(5 hunks)packages/vue-to-dsl/package.json(1 hunks)packages/vue-to-dsl/src/constants.ts(1 hunks)packages/vue-to-dsl/src/converter.ts(1 hunks)packages/vue-to-dsl/test/full/index.test.js(1 hunks)packages/vue-to-dsl/test/full/input/DemoPage.vue(1 hunks)packages/vue-to-dsl/test/full/input/createVm.vue(1 hunks)packages/vue-to-dsl/test/full/input/testCanvasRowCol.vue(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/vue-to-dsl/package.json
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/toolbars/generate-code/package.jsonpackages/build/vite-config/src/vite-plugins/devAliasPlugin.jspackages/toolbars/generate-code/src/Main.vue
📚 Learning: 2025-03-19T03:13:51.520Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1226
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-19T03:13:51.520Z
Learning: The CSS bug in packages/canvas/container/src/components/CanvasDivider.vue where verLeft already includes "px" but is being appended again in the style object will be fixed in a future update, as confirmed by gene9831.
Applied to files:
packages/vue-to-dsl/test/full/input/testCanvasRowCol.vue
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.
Applied to files:
packages/vue-to-dsl/test/full/input/DemoPage.vue
🧬 Code graph analysis (1)
packages/vue-to-dsl/src/converter.ts (6)
packages/vue-to-dsl/src/constants.ts (1)
defaultComponentMap(1-14)packages/vue-to-dsl/src/parser/index.ts (1)
parseSFC(4-41)packages/vue-to-dsl/src/parsers/templateParser.ts (1)
parseTemplate(231-238)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(399-427)packages/vue-to-dsl/src/parsers/styleParser.ts (1)
parseStyle(1-6)packages/vue-to-dsl/src/generator/index.ts (1)
generateSchema(140-165)
🔇 Additional comments (3)
packages/build/vite-config/src/vite-plugins/devAliasPlugin.js (1)
63-64: Dev alias '@opentiny/tiny-engine-vue-to-dsl' validated
Entry file exists, package.json name matches alias, no self-imports detected, and workspace includes the package.packages/vue-to-dsl/test/full/index.test.js (1)
7-9: Guard __dirname for ESM in Vitest tests
Replace the hard-coded __dirname with a runtime fallback:- const fullDir = __dirname + import path from 'path' + import { fileURLToPath } from 'url' + const fullDir = typeof __dirname !== 'undefined' + ? __dirname + : path.dirname(fileURLToPath(import.meta.url))Verify this change works under your Vitest setup (ESM/CJS) before merging.
packages/toolbars/generate-code/src/Main.vue (1)
44-45: Check package name and circular deps.Import looks good; just ensure this package is external to avoid circular deps noted in prior learnings.
Would you confirm that @opentiny/tiny-engine-vue-to-dsl is published as a separate package and not an alias to the current workspace?
| labelWidth="80px" | ||
| labelPosition="top" | ||
| :inline="false" | ||
| label-position="left " | ||
| label-width="150px" | ||
| style="border-radius: 0px" | ||
| > |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Duplicate/contradictory TinyForm props; trailing space breaks value.
You set both camelCase and kebab-case props, and label-position has a trailing space ("left "). Keep one style and fix the space to avoid incorrect rendering.
- <tiny-form
- labelWidth="80px"
- labelPosition="top"
- :inline="false"
- label-position="left "
- label-width="150px"
- style="border-radius: 0px"
- >
+ <tiny-form
+ :inline="false"
+ label-position="left"
+ label-width="150px"
+ style="border-radius: 0px"
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| labelWidth="80px" | |
| labelPosition="top" | |
| :inline="false" | |
| label-position="left " | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > | |
| <tiny-form | |
| :inline="false" | |
| label-position="left" | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/createVm.vue around lines 27 to 33, there
are duplicate/conflicting TinyForm props using both camelCase and kebab-case and
the label-position value contains a trailing space; remove the duplicated style
(choose either camelCase or kebab-case and keep only that style for
labelWidth/labelPosition), delete the redundant labelWidth or label-width entry,
and fix the label-position value to "left" (no trailing space) so props are not
contradictory and the value is valid.
| <span style="background-color: [object Event]; color: #8a8e99; font-size: 12px" | ||
| >温馨提示:页面左上角切换区域</span | ||
| > |
There was a problem hiding this comment.
Invalid inline style value renders literally.
background-color: [object Event] is a runtime/stringification bug and will leak to UI.
- <span style="background-color: [object Event]; color: #8a8e99; font-size: 12px"
+ <span style="background-color: transparent; color: #8a8e99; font-size: 12px"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span style="background-color: [object Event]; color: #8a8e99; font-size: 12px" | |
| >温馨提示:页面左上角切换区域</span | |
| > | |
| <span style="background-color: transparent; color: #8a8e99; font-size: 12px" | |
| >温馨提示:页面左上角切换区域</span | |
| > |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/createVm.vue around lines 49 to 51 the
inline style contains background-color: [object Event] which is coming from
passing an Event object into the style value; replace the literal/event-derived
value with a valid CSS color (or remove the inline style), and ensure the
component uses a proper bound style or class instead of stringifying an Event.
Locate where the value is set (likely an event handler passing the event),
change it to use the actual color source (e.g., a data prop like
currentRegionColor or event.target.value) and bind it with :style="{
backgroundColor: currentRegionColor }" or use a CSS class to avoid leaking
[object Event] into the DOM.
| <tiny-form | ||
| labelWidth="80px" | ||
| labelPosition="top" | ||
| :inline="false" | ||
| label-position="left " | ||
| label-width="150px" | ||
| style="border-radius: 0px" | ||
| > |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Repeat: TinyForm prop duplication.
- <tiny-form
- labelWidth="80px"
- labelPosition="top"
- :inline="false"
- label-position="left "
- label-width="150px"
- style="border-radius: 0px"
- >
+ <tiny-form
+ :inline="false"
+ label-position="left"
+ label-width="150px"
+ style="border-radius: 0px"
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <tiny-form | |
| labelWidth="80px" | |
| labelPosition="top" | |
| :inline="false" | |
| label-position="left " | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > | |
| <tiny-form | |
| :inline="false" | |
| label-position="left" | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/createVm.vue around lines 196 to 203
there are duplicated TinyForm props (labelWidth vs label-width and labelPosition
vs label-position) with conflicting values; remove the duplicate entries and
keep a single, consistent prop form (either camelCase or kebab-case) with the
intended value, and trim any stray whitespace (e.g., change label-position="left
" to label-position="left" or remove the camelCase duplicates and retain
label-width="150px" and label-position="left" if those are desired).
| <tiny-form | ||
| labelWidth="80px" | ||
| labelPosition="top" | ||
| :inline="false" | ||
| label-position="left " | ||
| label-width="150px" | ||
| style="border-radius: 0px" | ||
| > |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Repeat: TinyForm prop duplication.
- <tiny-form
- labelWidth="80px"
- labelPosition="top"
- :inline="false"
- label-position="left "
- label-width="150px"
- style="border-radius: 0px"
- >
+ <tiny-form
+ :inline="false"
+ label-position="left"
+ label-width="150px"
+ style="border-radius: 0px"
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <tiny-form | |
| labelWidth="80px" | |
| labelPosition="top" | |
| :inline="false" | |
| label-position="left " | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > | |
| <tiny-form | |
| :inline="false" | |
| label-position="left" | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| > |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/createVm.vue around lines 254 to 261, the
<tiny-form> element has duplicate/conflicting prop declarations (labelWidth vs
label-width and labelPosition vs label-position) and a trailing space in "left
". Remove the duplicated props so each prop appears only once, choose a single
consistent casing (kebab-case in templates or camelCase if using bind), keep the
intended values (e.g., label-width="150px" and label-position="top" or
labelWidth="150px" and :labelPosition="'top'"), and delete the extra space in
"left " so the final tag has a single, non-conflicting set of props.
| <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex"> | ||
| <tiny-icon-panel-mini | ||
| style="margin-right: 10px; width: 16px; height: 16px" | ||
| fill="currentColor" | ||
| ></tiny-icon-panel-mini> | ||
| <tiny-select | ||
| placeholder="请选择" | ||
| style="width: 200px; margin-right: 10px" | ||
| v-model="state.formData.diskType" | ||
| :options="[ | ||
| { value: '1', label: '黄金糕' }, | ||
| { value: '2', label: '双皮奶' } | ||
| ]" | ||
| ></tiny-select> | ||
| <tiny-input | ||
| placeholder="请输入" | ||
| style="width: 120px; margin-right: 10px" | ||
| v-model="state.inputValues.dataDiskSize" | ||
| ></tiny-input> | ||
| <span style="color: #575d6c; font-size: 12px; margin-right: 10px">GiB IOPS上限600,IOPS突发上限5,000</span> | ||
| <tiny-input placeholder="请输入" style="width: 120px" v-model="state.inputValues.diskLabel"></tiny-input> | ||
| </div> |
There was a problem hiding this comment.
Invalid v-for; missing alias and key.
v-for="() in state.dataDisk" is invalid and risks parse failures; also add a :key.
- <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex">
+ <div v-for="(disk, idx) in state.dataDisk" :key="idx" style="margin-top: 12px; display: flex">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex"> | |
| <tiny-icon-panel-mini | |
| style="margin-right: 10px; width: 16px; height: 16px" | |
| fill="currentColor" | |
| ></tiny-icon-panel-mini> | |
| <tiny-select | |
| placeholder="请选择" | |
| style="width: 200px; margin-right: 10px" | |
| v-model="state.formData.diskType" | |
| :options="[ | |
| { value: '1', label: '黄金糕' }, | |
| { value: '2', label: '双皮奶' } | |
| ]" | |
| ></tiny-select> | |
| <tiny-input | |
| placeholder="请输入" | |
| style="width: 120px; margin-right: 10px" | |
| v-model="state.inputValues.dataDiskSize" | |
| ></tiny-input> | |
| <span style="color: #575d6c; font-size: 12px; margin-right: 10px">GiB IOPS上限600,IOPS突发上限5,000</span> | |
| <tiny-input placeholder="请输入" style="width: 120px" v-model="state.inputValues.diskLabel"></tiny-input> | |
| </div> | |
| <div v-for="(disk, idx) in state.dataDisk" :key="idx" style="margin-top: 12px; display: flex"> | |
| <tiny-icon-panel-mini | |
| style="margin-right: 10px; width: 16px; height: 16px" | |
| fill="currentColor" | |
| ></tiny-icon-panel-mini> | |
| <tiny-select | |
| placeholder="请选择" | |
| style="width: 200px; margin-right: 10px" | |
| v-model="state.formData.diskType" | |
| :options="[ | |
| { value: '1', label: '黄金糕' }, | |
| { value: '2', label: '双皮奶' } | |
| ]" | |
| ></tiny-select> | |
| <tiny-input | |
| placeholder="请输入" | |
| style="width: 120px; margin-right: 10px" | |
| v-model="state.inputValues.dataDiskSize" | |
| ></tiny-input> | |
| <span style="color: #575d6c; font-size: 12px; margin-right: 10px">GiB IOPS上限600,IOPS突发上限5,000</span> | |
| <tiny-input placeholder="请输入" style="width: 120px" v-model="state.inputValues.diskLabel"></tiny-input> | |
| </div> |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/createVm.vue around lines 291 to 312, the
v-for is invalid because it lacks an alias and a key; change v-for="() in
state.dataDisk" to include an item (and optional index) like v-for="(disk, idx)
in state.dataDisk" and add a unique :key (for example :key="disk.id || idx");
adjust any v-model bindings inside the loop to reference the loop variable
(e.g., disk.property or state.dataDisk[idx].property) so each iteration binds
correctly.
| <tiny-form | ||
| labelWidth="80px" | ||
| labelPosition="top" | ||
| :inline="false" | ||
| label-position="left " | ||
| label-width="150px" | ||
| style="border-radius: 0px" | ||
| ></tiny-form> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Repeat: TinyForm prop duplication in footer.
- <tiny-form
- labelWidth="80px"
- labelPosition="top"
- :inline="false"
- label-position="left "
- label-width="150px"
- style="border-radius: 0px"
- ></tiny-form>
+ <tiny-form
+ :inline="false"
+ label-position="left"
+ label-width="150px"
+ style="border-radius: 0px"
+ ></tiny-form>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <tiny-form | |
| labelWidth="80px" | |
| labelPosition="top" | |
| :inline="false" | |
| label-position="left " | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| ></tiny-form> | |
| <tiny-form | |
| :inline="false" | |
| label-position="left" | |
| label-width="150px" | |
| style="border-radius: 0px" | |
| ></tiny-form> |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/createVm.vue around lines 338 to 345, the
<tiny-form> tag contains duplicated props (labelWidth vs label-width and
labelPosition vs label-position) with conflicting values and an extra trailing
space on "left "; remove the duplicate props and keep a single consistent prop
style (use kebab-case in templates: label-width and label-position), ensure only
one value for each (e.g., label-width="150px" and label-position="left" without
trailing space), and delete any redundant inline props so there is no ambiguity.
| const handleElementClick = wrap(function handleElementClick(elementId) { | ||
| this.state.uiState.selectedElement = elementId | ||
| }) | ||
|
|
||
| wrap({ handleElementClick }) | ||
| </script> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
handleElementClick is defined but never used. Wire it to exercise event conversion.
Hook at least one element to validate the PR’s event-handler conversion path.
- <canvas-col rowGap="20px" colGap="20px" :grow="true" :shrink="true" widthType="auto"></canvas-col>
+ <canvas-col
+ rowGap="20px"
+ colGap="20px"
+ :grow="true"
+ :shrink="true"
+ widthType="auto"
+ @click="handleElementClick('col-1')"
+ ></canvas-col>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleElementClick = wrap(function handleElementClick(elementId) { | |
| this.state.uiState.selectedElement = elementId | |
| }) | |
| wrap({ handleElementClick }) | |
| </script> | |
| <canvas-col | |
| rowGap="20px" | |
| colGap="20px" | |
| :grow="true" | |
| :shrink="true" | |
| widthType="auto" | |
| @click="handleElementClick('col-1')" | |
| ></canvas-col> |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/input/testCanvasRowCol.vue around lines 49 to
54, handleElementClick is defined and wrapped but never used; attach it to at
least one element in the template so the event-handler conversion path is
exercised. Edit the template to add a click binding (e.g., @click or v-on:click)
on a representative element and call handleElementClick with the element's id or
name (a literal or the element's bound id), keeping the existing wrap({
handleElementClick }) call intact so the test triggers the wrapped handler.
Ensure the element you modify exists in this test file and that the click
binding matches the handler signature.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
packages/toolbars/generate-code/src/Main.vue (3)
471-474: Normalize route for single-file import (ensure leading “/” and no doubles).- const safeRoute = `${rawName.replace(/\s+/g, '-').toLowerCase()}` + const safeRoute = `/${rawName.replace(/^\/*/, '').replace(/\s+/g, '-').toLowerCase()}`
3-3: Null-guard the icon binding to prevent runtime errors.
options.iconmay be undefined; accessingoptions.icon.downloadwill throw.- <toolbar-base content="出码" :icon="options.icon.download || options.icon" :options="options" @click-api="generate"> + <toolbar-base content="出码" :icon="options?.icon?.download ?? options?.icon" :options="options" @click-api="generate">
466-475: Single-file conversion must check result before use; current code can throw on...result.schema.Proceeding without verifying
result.schemarisks a TypeError. Also include warnings in success.- const result = await converter.convertFromString(text, file.name) + const result = await converter.convertFromString(text, file.name) + if (!result?.schema || (result.errors && result.errors.length)) { + useNotify({ + type: 'error', + title: '加载失败', + message: (result.errors || []).join('\n') || '转换失败' + }) + return + } + const warnCount = result.warnings?.length || 0 @@ - useNotify({ type: 'success', title: '导入成功', message: `已创建新页面:${rawName}` }) + useNotify({ + type: 'success', + title: '导入成功', + message: `已创建新页面:${rawName}${warnCount ? `(${warnCount} 条警告)` : ''}` + })Also applies to: 488-513
🧹 Nitpick comments (3)
packages/toolbars/generate-code/src/Main.vue (3)
13-23: Widen accepted file types or narrow the filename regex for consistency.You strip
.jsx|.tsxin code but the<input>only accepts.vue,.zip. Either allow these types or limit the regex to.vueonly.- <input ref="fileInputRef" type="file" accept=".vue,.zip" style="display: none" @change="handleFileChange" /> + <input ref="fileInputRef" type="file" accept=".vue,.zip,.jsx,.tsx" style="display: none" @change="handleFileChange" />
90-104: Prefer switching by page ID when available.Name-based lookup can be ambiguous; accept an optional
idand fall back toname.If useful, I can provide a small helper to resolve and switch by id first.
280-283: Dropconsole.errorin UI code; rely onuseNotify(aligns with toolbar practices).Keeps logs clean and follows prior guidance from preview toolbar.
- // eslint-disable-next-line no-console - console.error(error) useNotify({ type: 'error', title: '代码生成失败', message: error?.message || error }) @@ - // eslint-disable-next-line no-console - console.error(error) useNotify({ type: 'error', title: '代码保存失败', message: error?.message || error }) @@ - // eslint-disable-next-line no-console - console.error(error) useNotify({ type: 'error', title: '加载失败', message: error?.message || String(error) })Also applies to: 297-300, 535-537
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/toolbars/generate-code/src/Main.vue(5 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2024-10-10T02:47:46.239Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#850
File: packages/toolbars/preview/src/Main.vue:16-16
Timestamp: 2024-10-10T02:47:46.239Z
Learning: In `packages/toolbars/preview/src/Main.vue`, within the `preview` function, the `getMergeMeta` method is used at lines 64 and 65 to retrieve `engine.config` configurations.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
🔇 Additional comments (4)
packages/toolbars/generate-code/src/Main.vue (4)
25-31: Overwrite dialog wiring looks good.
87-89: Good: hidden file input ref for programmatic trigger.
545-611: Overwrite/creation flow looks solid; route normalization applied and tree refresh handled.
637-641: Styling LGTM.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/toolbars/generate-code/src/Main.vue (2)
18-20: Avoid arrow funcs in template handlers.Bind directly to reduce per-render allocations.
- <div class="option-item" @click="() => triggerUpload('file')">Vue 文件</div> - <div class="option-item" @click="() => triggerUpload('directory')">项目目录</div> - <div class="option-item" @click="() => triggerUpload('zip')">项目压缩包</div> + <div class="option-item" @click="triggerUpload('file')">Vue 文件</div> + <div class="option-item" @click="triggerUpload('directory')">项目目录</div> + <div class="option-item" @click="triggerUpload('zip')">项目压缩包</div>
697-697: Scoped style won’t affect popper content; use :deep selector.Popover DOM is teleported; scoped styles won’t apply.
-.option-popper { +:deep(.option-popper) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
mockServer/src/database/pages.dbis excluded by!**/*.db
📒 Files selected for processing (2)
packages/toolbars/generate-code/src/Main.vue(5 hunks)packages/vue-to-dsl/src/converter.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2024-10-10T02:47:46.239Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#850
File: packages/toolbars/preview/src/Main.vue:16-16
Timestamp: 2024-10-10T02:47:46.239Z
Learning: In `packages/toolbars/preview/src/Main.vue`, within the `preview` function, the `getMergeMeta` method is used at lines 64 and 65 to retrieve `engine.config` configurations.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/toolbars/generate-code/src/Main.vue
📚 Learning: 2025-01-14T06:50:21.158Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/data-function/parser.ts:191-195
Timestamp: 2025-01-14T06:50:21.158Z
Learning: The `newFn` function in `packages/canvas/render/src/data-function/parser.ts` has a known security issue with evaluating untrusted code through `data.value`. This was identified during code review but intentionally deferred as the original implementation was kept during code refactoring.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T06:58:38.661Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/data-utils.ts:8-12
Timestamp: 2025-01-14T06:58:38.661Z
Learning: The use of Function constructor in `packages/canvas/render/src/data-utils.ts` is part of migrated code and was intentionally kept as-is during migration, despite potential security concerns.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T08:45:57.032Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/application-function/global-state.ts:12-25
Timestamp: 2025-01-14T08:45:57.032Z
Learning: The code in `packages/canvas/render/src/application-function/global-state.ts` is migrated from an existing codebase and should be handled with care when making modifications.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T06:55:14.457Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/canvas-function/design-mode.ts:6-13
Timestamp: 2025-01-14T06:55:14.457Z
Learning: The code in `packages/canvas/render/src/canvas-function/design-mode.ts` is migrated code that should be preserved in its current form during the migration process. Refactoring suggestions for type safety and state management improvements should be considered in future PRs.
Applied to files:
packages/vue-to-dsl/src/converter.ts
🧬 Code graph analysis (1)
packages/vue-to-dsl/src/converter.ts (6)
packages/vue-to-dsl/src/constants.ts (1)
defaultComponentMap(1-16)packages/vue-to-dsl/src/parser/index.ts (1)
parseSFC(4-41)packages/vue-to-dsl/src/parsers/templateParser.ts (1)
parseTemplate(231-238)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(399-427)packages/vue-to-dsl/src/parsers/styleParser.ts (1)
parseStyle(1-6)packages/vue-to-dsl/src/generator/index.ts (2)
generateSchema(141-166)generateAppSchema(168-183)
🔇 Additional comments (13)
packages/toolbars/generate-code/src/Main.vue (6)
390-396: Guard app conversion result and surface failures early.Don’t proceed when conversion fails or returns an invalid shape.
const processAppSchema = async (appSchema: any) => { // 将 appSchema 应用到全局 const { appSchemaState } = useResource() + const warnCount = Array.isArray(appSchema?.warnings) ? appSchema.warnings.length : 0 + if (!appSchema || typeof appSchema !== 'object' || (!appSchema.pageSchema && !appSchema.i18n)) { + useNotify({ type: 'error', title: '加载失败', message: '转换失败' }) + return + }
516-523: Include warning count in success message.Show warning count when present.
- useNotify({ type: 'success', title: '导入成功', message: `已更新全局配置(未检测到页面)` }) + useNotify({ + type: 'success', + title: '导入成功', + message: `已更新全局配置(未检测到页面)${warnCount ? `(${warnCount} 条警告)` : ''}` + }) } else { await switchToPageByName(chosen?.meta?.name || chosen?.fileName) useNotify({ type: 'success', title: '导入成功', - message: `已创建页面并加载:${chosen?.meta?.name || '页面'}` + message: `已创建页面并加载:${chosen?.meta?.name || '页面'}${warnCount ? `(${warnCount} 条警告)` : ''}` }) }
529-536: Single-file: guard conversion errors and warn on success.Abort on errors; include warning count on success.
const processSingleFile = async (file: File, converter: VueToDslConverter) => { const text = await file.text() - const result = await converter.convertFromString(text, file.name) + const result = await converter.convertFromString(text, file.name) + if (!result?.schema || (result.errors && result.errors.length)) { + useNotify({ + type: 'error', + title: '加载失败', + message: (result.errors || []).join('\n') || '转换失败' + }) + return + } + const warnCount = result.warnings?.length || 0
534-534: Normalize route to start with “/” (aligns with ZIP/dir flow).- const safeRoute = `${rawName.replace(/\s+/g, '-').toLowerCase()}` + const safeRoute = `/${rawName.replace(/^\/+/, '').replace(/\s+/g, '-').toLowerCase()}`
574-575: Include warnings in success notices for single-file flow.- useNotify({ type: 'success', title: '导入成功', message: `已创建新页面:${rawName}` }) + useNotify({ + type: 'success', + title: '导入成功', + message: `已创建新页面:${rawName}${warnCount ? `(${warnCount} 条警告)` : ''}` + }) @@ - useNotify({ type: 'success', title: '导入成功', message: `已创建新页面:${rawName}` }) + useNotify({ + type: 'success', + title: '导入成功', + message: `已创建新页面:${rawName}${warnCount ? `(${warnCount} 条警告)` : ''}` + })Also applies to: 593-594
3-3: Fix potential runtime error: safely access icon.
options.iconcan be undefined (default props = {}), causing a crash. Align with Line 15.- <toolbar-base content="出码" :icon="options.icon.download || options.icon" :options="options" @click-api="generate"> + <toolbar-base content="出码" :icon="options?.icon?.download ?? options?.icon" :options="options" @click-api="generate">packages/vue-to-dsl/src/converter.ts (7)
16-20: Broaden custom parser typings to accept options; template may return array.customParsers?: { - template?: { parse: (code: string) => any } - script?: { parse: (code: string) => any } - style?: { parse: (code: string) => any } + template?: { parse: (code: string, options?: any) => any | any[] } + script?: { parse: (code: string, options?: any) => any } + style?: { parse: (code: string, options?: any) => any } }
62-71: Pass options to custom template parser and normalize to array.if (sfcResult.template) { try { templateSchema = this.options.customParsers?.template - ? this.options.customParsers.template.parse(sfcResult.template) + ? this.options.customParsers.template.parse(sfcResult.template, this.options as any) : parseTemplate(sfcResult.template, this.options as any) + if (!Array.isArray(templateSchema)) { + templateSchema = templateSchema ? [templateSchema] : [] + } } catch (error: any) {
76-82: Pass options to custom script parser (align with default).scriptSchema = this.options.customParsers?.script - ? this.options.customParsers.script.parse(scriptContent) + ? this.options.customParsers.script.parse(scriptContent, { + isSetup: !!sfcResult.scriptSetup, + ...(this.options as any) + }) : parseScript(scriptContent, {
101-103: Pass options to custom style parser.styleSchema = this.options.customParsers?.style - ? this.options.customParsers.style.parse(sfcResult.style) + ? this.options.customParsers.style.parse(sfcResult.style, this.options as any) : parseStyle(sfcResult.style, this.options as any)
110-116: Don’t mutate instance options with fileName; derive a local options object.- // Set fileName in options for schema generation - if (fileName) { - this.options.fileName = fileName.replace(/\.vue$/i, '') - } - - const schema = await generateSchema(templateSchema, scriptSchema, styleSchema, this.options as any) + const genOptions = { + ...(this.options as any), + fileName: fileName ? fileName.replace(/\.vue$/i, '') : (this.options as any).fileName + } + const schema = await generateSchema(templateSchema, scriptSchema, styleSchema, genOptions)
283-286: Critical: avoid executing untrusted code via Function(...) for store state. Use a safe object-literal parser.- const objText = stateMatch[1] - const stateObj = Function(`return (${objText})`)() + const objText = stateMatch[1] + const stateObj = safeParseObjectLiteral(objText) @@ - const objText = stateMatch[1] - const stateObj = Function(`return (${objText})`)() + const objText = stateMatch[1] + const stateObj = safeParseObjectLiteral(objText) @@ - const objText = stateMatch[1] - const stateObj = Function(`return (${objText})`)() + const objText = stateMatch[1] + const stateObj = safeParseObjectLiteral(objText)Additions required elsewhere in this module:
// at top-level imports import { parse as babelParse } from '@babel/parser' // helper (outside the class) function safeParseObjectLiteral(objText: string): any { try { const ast = babelParse(`(${objText})`, { sourceType: 'module', plugins: ['typescript'] as any }) // @ts-ignore const expr: any = (ast as any).program.body[0].expression if (!expr || expr.type !== 'ObjectExpression') return {} const toValue = (node: any): any => { switch (node.type) { case 'StringLiteral': case 'NumericLiteral': case 'BooleanLiteral': case 'NullLiteral': return node.value case 'ObjectExpression': return Object.fromEntries( node.properties.map((p: any) => [p.key.name || p.key.value, toValue(p.value)]) ) case 'ArrayExpression': return node.elements.map((el: any) => toValue(el)) default: return undefined } } return toValue(expr) } catch { return {} } }Also applies to: 487-489, 713-714
548-588: Clean up temp directory after ZIP conversion (leak).- // 1) Unzip into a temp directory - const tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'vue-to-dsl-')) - const zip = await JSZip.loadAsync(zipBuffer as any) + // 1) Unzip into a temp directory + const tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'vue-to-dsl-')) + try { + const zip = await JSZip.loadAsync(zipBuffer as any) @@ - // 3) Delegate to convertAppDirectory - const schema = await this.convertAppDirectory(appRoot) - return schema + // 3) Delegate to convertAppDirectory + const schema = await this.convertAppDirectory(appRoot) + return schema + } finally { + await fs.rm(tmpBase, { recursive: true, force: true }).catch(() => {}) + }
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/vue-to-dsl/src/converter.ts (1)
612-626: Dynamic regex from .gitignore patterns can be ReDoS-prone; prefer a battle-tested lib.Use ignore/picomatch/minimatch to parse .gitignore semantics safely.
Example:
// npm i ignore (or ensure it’s available in browser build) import ignore from 'ignore' const ig = ignore().add(gitignoreContent) return (p: string) => ig.ignores(p) === false
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/vue-to-dsl/src/converter.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
📚 Learning: 2025-01-14T06:50:21.158Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/data-function/parser.ts:191-195
Timestamp: 2025-01-14T06:50:21.158Z
Learning: The `newFn` function in `packages/canvas/render/src/data-function/parser.ts` has a known security issue with evaluating untrusted code through `data.value`. This was identified during code review but intentionally deferred as the original implementation was kept during code refactoring.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T06:58:38.661Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/data-utils.ts:8-12
Timestamp: 2025-01-14T06:58:38.661Z
Learning: The use of Function constructor in `packages/canvas/render/src/data-utils.ts` is part of migrated code and was intentionally kept as-is during migration, despite potential security concerns.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T08:45:57.032Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/application-function/global-state.ts:12-25
Timestamp: 2025-01-14T08:45:57.032Z
Learning: The code in `packages/canvas/render/src/application-function/global-state.ts` is migrated from an existing codebase and should be handled with care when making modifications.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T06:55:14.457Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/canvas-function/design-mode.ts:6-13
Timestamp: 2025-01-14T06:55:14.457Z
Learning: The code in `packages/canvas/render/src/canvas-function/design-mode.ts` is migrated code that should be preserved in its current form during the migration process. Refactoring suggestions for type safety and state management improvements should be considered in future PRs.
Applied to files:
packages/vue-to-dsl/src/converter.ts
🧬 Code graph analysis (1)
packages/vue-to-dsl/src/converter.ts (6)
packages/vue-to-dsl/src/constants.ts (1)
defaultComponentMap(1-16)packages/vue-to-dsl/src/parser/index.ts (1)
parseSFC(4-41)packages/vue-to-dsl/src/parsers/templateParser.ts (1)
parseTemplate(231-238)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(399-427)packages/vue-to-dsl/src/parsers/styleParser.ts (1)
parseStyle(1-6)packages/vue-to-dsl/src/generator/index.ts (2)
generateSchema(141-166)generateAppSchema(168-183)
🪛 ast-grep (0.39.5)
packages/vue-to-dsl/src/converter.ts
[warning] 621-621: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexString})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
[warning] 624-624: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexString}(/.*)?$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔇 Additional comments (9)
packages/vue-to-dsl/src/converter.ts (9)
12-25: Broaden custom parser typings to accept options and async; template may return array.Align types with actual usage and expected extensibility.
export interface VueToSchemaOptions { componentMap?: Record<string, string> preserveComments?: boolean strictMode?: boolean customParsers?: { - template?: { parse: (code: string) => any } - script?: { parse: (code: string) => any } - style?: { parse: (code: string) => any } + template?: { parse: (code: string, options?: any) => any | any[] | Promise<any | any[]> } + script?: { parse: (code: string, options?: any) => any | Promise<any> } + style?: { parse: (code: string, options?: any) => any | Promise<any> } } fileName?: string path?: string title?: string description?: string }
62-71: Pass options to custom template parser and normalize to array; support async.Prevents downstream shape mismatches.
if (sfcResult.template) { try { - templateSchema = this.options.customParsers?.template - ? this.options.customParsers.template.parse(sfcResult.template) - : parseTemplate(sfcResult.template, this.options as any) + if (this.options.customParsers?.template) { + const res = await this.options.customParsers.template.parse( + sfcResult.template, + this.options as any + ) + templateSchema = Array.isArray(res) ? res : res ? [res] : [] + } else { + templateSchema = parseTemplate(sfcResult.template, this.options as any) + } } catch (error: any) {
76-82: Pass options to custom script parser; support async.Keeps parity with default parser.
- scriptSchema = this.options.customParsers?.script - ? this.options.customParsers.script.parse(scriptContent) - : parseScript(scriptContent, { - isSetup: !!sfcResult.scriptSetup, - ...(this.options as any) - }) + scriptSchema = this.options.customParsers?.script + ? await this.options.customParsers.script.parse(scriptContent, { + isSetup: !!sfcResult.scriptSetup, + ...(this.options as any) + }) + : parseScript(scriptContent, { + isSetup: !!sfcResult.scriptSetup, + ...(this.options as any) + })
99-108: Pass options to custom style parser; preserve scoped/lang from SFC blocks; support async.Otherwise scoped/lang are lost.
if (sfcResult.style) { try { - styleSchema = this.options.customParsers?.style - ? this.options.customParsers.style.parse(sfcResult.style) - : parseStyle(sfcResult.style, this.options as any) + const styleOpts = { + ...(this.options as any), + scoped: Array.isArray(sfcResult.styleBlocks) + ? sfcResult.styleBlocks.some((b: any) => !!b.scoped) + : false, + lang: + (Array.isArray(sfcResult.styleBlocks) && (sfcResult.styleBlocks[0] as any)?.lang) || + 'css' + } + styleSchema = this.options.customParsers?.style + ? await this.options.customParsers.style.parse(sfcResult.style, styleOpts) + : parseStyle(sfcResult.style, styleOpts) } catch (error: any) {
110-116: Don’t mutate instance options; derive a generator options object.Avoids cross-call side effects.
- // Set fileName in options for schema generation - if (fileName) { - this.options.fileName = fileName.replace(/\.vue$/i, '') - } - - const schema = await generateSchema(templateSchema, scriptSchema, styleSchema, this.options as any) + const genOptions = { + ...(this.options as any), + fileName: fileName ? fileName.replace(/\.vue$/i, '') : (this.options as any).fileName + } + const schema = await generateSchema(templateSchema, scriptSchema, styleSchema, genOptions)
585-588: Ensure temp dir cleanup after unzip.Avoid leaked temp directories on errors.
- // 3) Delegate to convertAppDirectory - const schema = await this.convertAppDirectory(appRoot) - return schema + // 3) Delegate to convertAppDirectory with cleanup + let schema: any + try { + schema = await this.convertAppDirectory(appRoot) + } finally { + await fs.rm(tmpBase, { recursive: true, force: true }).catch(() => {}) + } + return schema
281-286: RCE: avoid evaluating untrusted code with Function when parsing store state.Replace with a safe AST-based object literal parser.
- const objText = stateMatch[1] - const stateObj = Function(`return (${objText})`)() - entry.state = stateObj + const objText = stateMatch[1] + const stateObj = safeParseObjectLiteral(objText) + entry.state = stateObjAdd this helper and import (top-level, outside the class):
import { parse as babelParse } from '@babel/parser' function safeParseObjectLiteral(objText: string): any { try { const ast: any = babelParse(`(${objText})`, { sourceType: 'module', plugins: ['typescript'] as any }) const expr = ast.program.body[0].expression if (!expr || expr.type !== 'ObjectExpression') return {} const toValue = (node: any): any => { switch (node?.type) { case 'StringLiteral': case 'NumericLiteral': case 'BooleanLiteral': return node.value case 'NullLiteral': return null case 'ObjectExpression': return Object.fromEntries( node.properties .filter((p: any) => p.type === 'ObjectProperty') .map((p: any) => [ p.key.type === 'Identifier' ? p.key.name : p.key.value, toValue(p.value) ]) ) case 'ArrayExpression': return node.elements.map((el: any) => toValue(el)) default: return undefined } } const v = toValue(expr) return v && typeof v === 'object' ? v : {} } catch { return {} } }
487-489: Apply the same safe parsing when unzipping app (browser path).Mirror the fix here.
- const objText = stateMatch[1] - const stateObj = Function(`return (${objText})`)() - entry.state = stateObj + const objText = stateMatch[1] + const stateObj = safeParseObjectLiteral(objText) + entry.state = stateObj
5-6: No mismatch — two distinct exports are intended. generator imports defaultComponentsMap (array of package/component descriptors); converter imports defaultComponentMap (tag → component name map). No change required.
| const fileEntries: string[] = [] | ||
| const writeTasks: Promise<any>[] = [] | ||
| zip.forEach((relPath, file) => { | ||
| // Skip macOS metadata | ||
| if (relPath.startsWith('__MACOSX/')) return | ||
| const outPath = path.join(tmpBase, relPath) | ||
| if (file.dir) { | ||
| writeTasks.push(fs.mkdir(outPath, { recursive: true })) | ||
| } else { | ||
| fileEntries.push(relPath) | ||
| writeTasks.push( | ||
| (async () => { | ||
| await fs.mkdir(path.dirname(outPath), { recursive: true }) | ||
| const content = await file.async('nodebuffer') | ||
| await fs.writeFile(outPath, content) | ||
| })() | ||
| ) | ||
| } | ||
| }) |
There was a problem hiding this comment.
Zip Slip: sanitize zip paths before writing to disk.
Prevent directory traversal during unzip.
- const outPath = path.join(tmpBase, relPath)
- if (file.dir) {
- writeTasks.push(fs.mkdir(outPath, { recursive: true }))
- } else {
- fileEntries.push(relPath)
- writeTasks.push(
- (async () => {
- await fs.mkdir(path.dirname(outPath), { recursive: true })
- const content = await file.async('nodebuffer')
- await fs.writeFile(outPath, content)
- })()
- )
- }
+ const destPath = path.resolve(tmpBase, relPath)
+ if (!destPath.startsWith(tmpBase + path.sep) && destPath !== tmpBase) {
+ return // block path traversal
+ }
+ if (file.dir) {
+ writeTasks.push(fs.mkdir(destPath, { recursive: true }))
+ } else {
+ fileEntries.push(relPath)
+ writeTasks.push(
+ (async () => {
+ await fs.mkdir(path.dirname(destPath), { recursive: true })
+ const content = await file.async('nodebuffer')
+ await fs.writeFile(destPath, content)
+ })()
+ )
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const fileEntries: string[] = [] | |
| const writeTasks: Promise<any>[] = [] | |
| zip.forEach((relPath, file) => { | |
| // Skip macOS metadata | |
| if (relPath.startsWith('__MACOSX/')) return | |
| const outPath = path.join(tmpBase, relPath) | |
| if (file.dir) { | |
| writeTasks.push(fs.mkdir(outPath, { recursive: true })) | |
| } else { | |
| fileEntries.push(relPath) | |
| writeTasks.push( | |
| (async () => { | |
| await fs.mkdir(path.dirname(outPath), { recursive: true }) | |
| const content = await file.async('nodebuffer') | |
| await fs.writeFile(outPath, content) | |
| })() | |
| ) | |
| } | |
| }) | |
| const fileEntries: string[] = [] | |
| const writeTasks: Promise<any>[] = [] | |
| zip.forEach((relPath, file) => { | |
| // Skip macOS metadata | |
| if (relPath.startsWith('__MACOSX/')) return | |
| const destPath = path.resolve(tmpBase, relPath) | |
| if (!destPath.startsWith(tmpBase + path.sep) && destPath !== tmpBase) { | |
| return // block path traversal | |
| } | |
| if (file.dir) { | |
| writeTasks.push(fs.mkdir(destPath, { recursive: true })) | |
| } else { | |
| fileEntries.push(relPath) | |
| writeTasks.push( | |
| (async () => { | |
| await fs.mkdir(path.dirname(destPath), { recursive: true }) | |
| const content = await file.async('nodebuffer') | |
| await fs.writeFile(destPath, content) | |
| })() | |
| ) | |
| } | |
| }) |
| const stateObj = Function(`return (${objText})`)() | ||
| entry.state = stateObj |
There was a problem hiding this comment.
Apply the same safe parsing in directory import (browser).
- const objText = stateMatch[1]
- const stateObj = Function(`return (${objText})`)()
+ const objText = stateMatch[1]
+ const stateObj = safeParseObjectLiteral(objText)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/vue-to-dsl/src/converter.ts around lines 763-764, replace the unsafe
usage of Function(`return (${objText})`)() with the project's existing safe
parsing helper used elsewhere for browser directory imports: import/Call that
safe parser (e.g., safeParseExpression or parseExpression) and wrap it in a
try/catch to assign entry.state only on successful parse, returning a sensible
fallback (null or {}) and logging or preserving the original text on parse
failure.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (11)
packages/vue-to-dsl/test/testcases/001_simple/input/component.vue (1)
1-35: Past review comment is outdated and no longer applicable.The past review flagged issues with a
countvariable and reactive/ref mismatches, but the current code usesswitchStatuswith correct reactive usage. The file appears to have been completely rewritten since that review.packages/vue-to-dsl/test/testcases/002_createVM/input/component.vue (3)
26-33: Duplicate and conflicting form props: labelWidth/label-width, labelPosition/label-positionMultiple
TinyForminstances declare both camelCase and kebab-case versions of the same props with different values. Vue normalizes these to the same prop, so the last declaration wins unpredictably. Additionally,"left "contains a trailing space.Also applies to: 82-89, 196-203, 254-261, 278-285, 330-337
49-51: Invalid CSS in inline style: background-color: [object Event]This runtime artifact will render invalid CSS.
287-304: Invalid v-for syntax: empty tuple "() in …"The v-for uses an empty tuple which is invalid syntax, and lacks a
:keybinding.packages/vue-to-dsl/test/testcases/002_createVM/expected/schema.json (2)
60-67: Duplicate/colliding props: both camelCase and kebab-case emitted.Props include both
labelWidth+label-widthandlabelPosition+label-position. The converter should normalize to a single canonical casing and deduplicate.Also applies to: 198-205, 537-544, 666-673, 745-752, 906-913
118-122: Data corruption: style contains '[object Event]'.The converter leaked a non-serializable value into style serialization, producing invalid CSS in the DSL schema.
packages/vue-to-dsl/src/generator/index.ts (1)
118-131: Sanitizer is a no‑op but comment claims newline removalEither implement selective sanitization (skip
cssand any{ type: 'JSFunction' }bodies) or update the comment to reflect current behavior. Past review explicitly asked to preservecssand function strings.-// Deeply sanitize all string values in the schema +// Deeply sanitize string values (skip css and JSFunction bodies) function sanitizeSchemaStrings(obj: any): any { if (obj === null || obj === undefined) return obj - if (typeof obj === 'string') return obj + if (typeof obj === 'string') return obj if (Array.isArray(obj)) return obj.map((v) => sanitizeSchemaStrings(v)) if (typeof obj === 'object') { - const out: any = Array.isArray(obj) ? [] : {} + const out: any = {} Object.keys(obj).forEach((k) => { - out[k] = sanitizeSchemaStrings(obj[k]) + const v = (obj as any)[k] + if (k === 'css' && typeof v === 'string') { + out[k] = v + } else if (v && typeof v === 'object' && v.type === 'JSFunction' && typeof v.value === 'string') { + out[k] = v + } else { + out[k] = sanitizeSchemaStrings(v) + } }) return out } return obj } @@ - // sanitize all strings to remove newlines in the final output + // sanitize (no-op for css/JSFunction) const sanitized = sanitizeSchemaStrings(schema)Also applies to: 150-151
packages/vue-to-dsl/src/parsers/templateParser.ts (3)
140-143: Align v-if/v-show to JSExpression objects for consistencyElsewhere you emit
{ type: 'JSExpression', value }. Do the same forschema.conditionandv-showfor uniform consumers.- schema.condition = prop.exp ? prop.exp.content : 'true' + schema.condition = { type: 'JSExpression', value: prop.exp ? prop.exp.content : 'true' } @@ - schema.props['v-show'] = prop.exp ? prop.exp.content : 'true' + schema.props['v-show'] = { type: 'JSExpression', value: prop.exp ? prop.exp.content : 'true' }Also applies to: 155-157
148-153: Fix invalid/unsafe regex ([^]) in v-for parsing
[^]is a negated empty class and matches anything; Biome flags it. Replace with a safe any‑char class.- const match = - exp.match(/^[^]*?(?:\)|\S)\s+(?:in|of)\s+([^]+)$/) || exp.match(/^(?:[^]+?)\s+(?:in|of)\s+([^]+)$/) + const match = + exp.match(/^[\s\S]*?(?:\)|\S)\s+(?:in|of)\s+([\s\S]+)$/) || + exp.match(/^(?:[\s\S]+?)\s+(?:in|of)\s+([\s\S]+)$/)(Alternatively, slice by last " in " / " of " to avoid brittle regex.)
207-226: Icon normalization drops existing props/handlersOverwriting
schema.propsloses classes, aria, and events parsed earlier (breaks behavior/accessibility). Preserve previous props and addname.- const styleVal = schema.props && typeof schema.props.style === 'string' ? schema.props.style : undefined - // Rebuild props: keep style if present; set name; drop other raw attributes like fill - schema.componentName = 'Icon' - schema.props = {} - if (styleVal) schema.props.style = styleVal - schema.props.name = iconName + const prev = { ...(schema.props || {}) } + schema.componentName = 'Icon' + schema.props = { ...prev, name: iconName } + // Optionally drop raw SVG-only attrs if needed: + // const { fill, stroke, ...rest } = schema.props; schema.props = { ...rest, name: iconName }packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
174-199: Generalize state extraction (ref/reactive) and run before method detectionCurrently only variables literally named “state” are captured; typical
const count = ref(1)is missed. Also, detect state/computed before methods. This prevents misclassification and captures Composition API patterns.- function assignStateIfNamedState(name: string, init: any, result: any): boolean { - if (name !== 'state') return false - const initCode = getNodeValue(init) - if (isVueReactiveCall(init, 'reactive')) { - const firstArg = init.arguments && init.arguments[0] - if (t.isObjectExpression(firstArg)) { - firstArg.properties.forEach((prop: any) => { - if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) { - const propName = prop.key.name - const propValue = prop.value ? getNodeValue(prop.value) : undefined - result.state[propName] = { type: 'reactive', value: propValue } - } - }) - } else { - result.state[name] = { type: 'reactive', value: initCode } - } - return true - } - if (isVueReactiveCall(init, 'ref')) { - result.state[name] = { type: 'ref', value: initCode } - return true - } - // normal non-reactive assignment to state - result.state[name] = { type: 'normal', value: initCode } - return true -} +function assignStateFromVueCalls(name: string, init: any, result: any): boolean { + const initCode = getNodeValue(init) + // reactive(...) + if (isVueReactiveCall(init, 'reactive')) { + const firstArg = init.arguments?.[0] + if (t.isObjectExpression(firstArg)) { + firstArg.properties.forEach((prop: any) => { + if (t.isObjectProperty(prop) && (t.isIdentifier(prop.key) || t.isStringLiteral(prop.key))) { + const propName = t.isIdentifier(prop.key) ? prop.key.name : prop.key.value + const propValue = prop.value ? getNodeValue(prop.value) : undefined + result.state[propName] = { type: 'reactive', value: propValue } + } + }) + } else { + result.state[name] = { type: 'reactive', value: initCode } + } + return true + } + // ref(...) + if (isVueReactiveCall(init, 'ref')) { + result.state[name] = { type: 'ref', value: initCode } + return true + } + return false +} @@ -function handleVariableDeclarator(name: string, init: any, result: any, source: string) { - // 1) function-like assignments become methods - if (addMethodFromFunctionLike(name, init, result, source)) return - // 2) state-only extraction - if (assignStateIfNamedState(name, init, result)) return - // 3) computed regardless of name - if (assignComputedIfComputed(name, init, result, source)) return - // 4) otherwise ignored for state (per requirement), no-op -} +function handleVariableDeclarator(name: string, init: any, result: any, source: string) { + // 1) state/computed first + if (assignStateFromVueCalls(name, init, result)) return + if (assignComputedIfComputed(name, init, result, source)) return + // 2) then methods + if (addMethodFromFunctionLike(name, init, result, source)) return + // 3) ignore others +}Run to confirm no remaining references to the old helper and that
ref/reactivevars are captured:#!/bin/bash rg -n "assignStateIfNamedState|assignStateFromVueCalls|ref\\(|reactive\\(" packages/vue-to-dsl -C2Also applies to: 214-222
🧹 Nitpick comments (7)
packages/vue-to-dsl/test/testcases/001_simple/input/component.vue (1)
11-11: Remove redundant import of compiler macros.In
<script setup>,definePropsanddefineEmitsare compiler macros automatically available without import.Apply this diff:
-import { defineProps, defineEmits } from 'vue'packages/vue-to-dsl/test/testcases/004_dashboard/input/component.vue (1)
88-90: Unused handlergo— keep intentionally or remove
go(item)is not referenced in the template. If it exists only to validate method extraction in tests, keep and add a brief comment; otherwise remove to avoid dead code.packages/vue-to-dsl/test/testcases/005_survey/input/component.vue (2)
52-55: Injection may be null — add a defensive fallback
vue.inject(I18nInjectionKey)can beundefinedif not provided. Guard to avoid runtime errors in isolated tests.-const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode() +const injected = vue.inject(I18nInjectionKey) +const lowcode = injected?.lowcode?.() +if (!lowcode) { + // Fallback for tests without provider + const noWrap = (x: any) => x + var t = (s: string) => s + var lowcodeWrap = (_: any, __: any) => (fn: any) => fn + var stores = {} +} else { + var { t, lowcodeWrap, stores } = lowcode +}
45-47: Minor: duplicate imports and unusedtYou import the whole
vuenamespace and also nameddefineProps/defineEmits; you can drop the latter and reference fromvue. Alsotis declared but unused.-import { defineProps, defineEmits } from 'vue' +// use vue.defineProps / vue.defineEmits via the namespace importpackages/vue-to-dsl/src/generator/index.ts (1)
21-41: State conversion: keep non‑string reactive values as‑isCurrent logic handles string primitives well. If
reactivecontains arrays/objects,convertToPlainValuereturns them untouched — that’s OK. Add a short comment to document this intent for readers.- case 'reactive': - result[key] = convertToPlainValue(stateItem.value) + case 'reactive': + // If value is non-string (object/array), preserve as-is + result[key] = convertToPlainValue(stateItem.value)packages/vue-to-dsl/src/parsers/scriptParser.ts (2)
48-89: Value conversion gaps (TemplateLiteral, Identifier, RegExp, BigInt)Edge cases stringify to "undefined". Add common literal/expr support to reduce surprises.
function getNodeValue(node: any): any { + if (t.isTemplateLiteral(node)) { + const cooked = node.quasis.map((q) => q.value.cooked ?? '').join('${}') + return '`' + cooked + '`' + } + if (t.isIdentifier(node)) return node.name + if ((t as any).isBigIntLiteral?.(node)) return String((node as any).value) + if (t.isRegExpLiteral(node)) return `/${node.pattern}/${node.flags}` if (t.isStringLiteral(node)) return node.value if (t.isNumericLiteral(node)) return node.value
460-463: Broaden parser plugins for robustness, but apply consistently across parsersThe suggestion to add
topLevelAwaitandimportMetaplugins is technically sound—these are standard Babel parser plugins that would help handle Vue projects using these ES/TS features. However, verification reveals two issues:
No current usage or failures: These features aren't used anywhere in the codebase, and no parse failures are reported by existing integration tests.
Inconsistency: The identical parser configuration (
plugins: ['typescript', 'jsx']) appears in bothscriptParser.tsandtemplateParser.ts(line 124). IfparseScriptis broadened,templateParsershould be updated similarly for consistency.If you proceed with this optional enhancement, apply it to both parsers and consider adding test cases that verify the parser handles these features correctly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
packages/vue-to-dsl/src/generator/index.ts(1 hunks)packages/vue-to-dsl/src/parsers/scriptParser.ts(1 hunks)packages/vue-to-dsl/src/parsers/templateParser.ts(1 hunks)packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/001_simple/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/002_createVM/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/002_createVM/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/003_login/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/003_login/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/004_dashboard/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/005_survey/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/005_survey/input/component.vue(1 hunks)packages/vue-to-dsl/test/testcases/index.test.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/vue-to-dsl/test/testcases/index.test.js
🧰 Additional context used
🧬 Code graph analysis (3)
packages/vue-to-dsl/src/generator/index.ts (3)
packages/vue-generator/src/generator/vue/sfc/generateAttribute.js (1)
props(596-596)packages/vue-to-dsl/src/index.ts (2)
generateSchema(5-5)generateAppSchema(5-5)packages/vue-to-dsl/src/constants.ts (1)
defaultComponentsMap(18-217)
packages/vue-to-dsl/src/parsers/scriptParser.ts (2)
packages/vue-to-dsl/src/index.ts (1)
parseScript(6-6)packages/vue-to-dsl/src/parsers/index.ts (1)
parseScript(2-2)
packages/vue-to-dsl/src/parsers/templateParser.ts (5)
packages/vue-generator/src/generator/vue/sfc/generateAttribute.js (1)
props(596-596)packages/vue-generator/src/generator/vue/sfc/generateTemplate.js (1)
name(40-40)packages/vue-generator/src/templates/vue-template/index.js (1)
value(30-30)packages/vue-to-dsl/src/index.ts (1)
parseTemplate(6-6)packages/vue-to-dsl/src/parsers/index.ts (1)
parseTemplate(1-1)
🪛 Biome (2.1.2)
packages/vue-to-dsl/src/parsers/templateParser.ts
[error] 149-149: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 149-149: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 149-149: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 149-149: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
🔇 Additional comments (8)
packages/vue-to-dsl/test/testcases/001_simple/input/component.vue (1)
17-34: Test fixture patterns are appropriate for converter validation.The
lowcodeWrappattern, nestedsetupfunction, and immediate invocation are non-idiomatic Vue but appear intentional for testing the DSL converter's ability to handle various code structures and extract lifecycle hooks, state, and methods correctly.packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json (1)
1-60: Expected schema correctly represents the DSL conversion.The schema accurately captures the Vue SFC structure:
- Initial state (
switchStatus: true) from reactive declaration- Methods (
onClickNew) with correct toggle logic- Lifecycle (
setup) preserving console.log and state mutation- CSS extracted from scoped styles
- Template tree with proper v-model → modelValue and @click → onClick conversions
- Component IDs ensure stable references for test validation
packages/vue-to-dsl/test/testcases/005_survey/expected/schema.json (1)
1-223: LGTM — schema matches expected DSL shapeBindings, JSFunction formatting, and ids look coherent with generator output.
packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json (1)
1-408: LGTM — expected dashboard schema is consistentProps casing, JSExpression usage, and layout tree align with parser/generator conventions.
packages/vue-to-dsl/src/generator/index.ts (2)
133-155: Public API composition looks goodFlow from script/style/template into the Page schema is clear and modular.
157-170: App schema defaults are sensibleDefault i18n/utils/dataSource/globalState and components map wiring look fine.
packages/vue-to-dsl/src/parsers/templateParser.ts (1)
228-246: Parser structure and node recursion look solidGeneral element/text/interpolation handling is clear and idiomatic.
packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
344-371: I need to gather more information about the current implementation and test expectations.Now let me look at the test file to see what assertions it makes:
Now let me search the codebase for the test assertions and current implementation:
Let me run a shell script to inspect the actual codebase:Based on my web searches, I have not found specific information about the
vue-to-dslcodebase. Let me now generate the final rewritten review comment based on the information gathered and the context from the test output I initially received.Extract actual state properties from Options API
data()functionThe current implementation uses a placeholder string and loses the actual state properties. The proposed diff correctly extracts the returned object from
data()and populatesresult.statewith the actual properties using Babel AST traversal.However, verification requires confirming:
- The
getNodeValue()helper function exists in the codebase and correctly extracts literal values- The test assertions in
converter.test.jsactually validate thatschema.state.message === 'Hello from Options API'(and similar state properties)- The implementation handles edge cases like arrow functions, nested properties, and computed values
These details could not be confirmed from available codebase context.
| // Generate an 8-char id with lowercase letters and digits | ||
| function generateId(): string { | ||
| let s = '' | ||
| while (s.length < 8) s += Math.random().toString(36).slice(2) | ||
| return s.slice(0, 8) | ||
| } | ||
|
|
||
| // Recursively assign id to nodes with componentName | ||
| function assignComponentIds(node: any): void { | ||
| if (!node || typeof node !== 'object') return | ||
| if (typeof node.componentName === 'string') { | ||
| if (!node.id) node.id = generateId() | ||
| } | ||
| if (Array.isArray(node.children)) node.children.forEach(assignComponentIds) | ||
| } |
There was a problem hiding this comment.
IDs are non‑deterministic — make them stable across runs
generateId() uses Math.random(), violating the PR goal of “stable references” and making snapshot tests flaky. Derive ids from traversal path to be deterministic.
+import { createHash } from 'crypto'
@@
-// Generate an 8-char id with lowercase letters and digits
-function generateId(): string {
- let s = ''
- while (s.length < 8) s += Math.random().toString(36).slice(2)
- return s.slice(0, 8)
-}
+// Generate a deterministic 8-char id from a stable input (path + componentName)
+function generateIdFrom(input: string): string {
+ return createHash('md5').update(input).digest('hex').slice(0, 8)
+}
@@
-// Recursively assign id to nodes with componentName
-function assignComponentIds(node: any): void {
- if (!node || typeof node !== 'object') return
- if (typeof node.componentName === 'string') {
- if (!node.id) node.id = generateId()
- }
- if (Array.isArray(node.children)) node.children.forEach(assignComponentIds)
-}
+// Recursively assign deterministic ids using preorder path
+function assignComponentIds(node: any, path = '0'): void {
+ if (!node || typeof node !== 'object') return
+ if (typeof node.componentName === 'string') {
+ if (!node.id) node.id = generateIdFrom(`${path}:${node.componentName}`)
+ }
+ if (Array.isArray(node.children)) {
+ node.children.forEach((child: any, idx: number) => assignComponentIds(child, `${path}.${idx}`))
+ }
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Generate an 8-char id with lowercase letters and digits | |
| function generateId(): string { | |
| let s = '' | |
| while (s.length < 8) s += Math.random().toString(36).slice(2) | |
| return s.slice(0, 8) | |
| } | |
| // Recursively assign id to nodes with componentName | |
| function assignComponentIds(node: any): void { | |
| if (!node || typeof node !== 'object') return | |
| if (typeof node.componentName === 'string') { | |
| if (!node.id) node.id = generateId() | |
| } | |
| if (Array.isArray(node.children)) node.children.forEach(assignComponentIds) | |
| } | |
| import { createHash } from 'crypto' | |
| // Generate a deterministic 8-char id from a stable input (path + componentName) | |
| function generateIdFrom(input: string): string { | |
| return createHash('md5').update(input).digest('hex').slice(0, 8) | |
| } | |
| // Recursively assign deterministic ids using preorder path | |
| function assignComponentIds(node: any, path = '0'): void { | |
| if (!node || typeof node !== 'object') return | |
| if (typeof node.componentName === 'string') { | |
| if (!node.id) node.id = generateIdFrom(`${path}:${node.componentName}`) | |
| } | |
| if (Array.isArray(node.children)) { | |
| node.children.forEach((child: any, idx: number) => assignComponentIds(child, `${path}.${idx}`)) | |
| } | |
| } |
| if (t.isReturnStatement(statement) && t.isObjectExpression(statement.argument)) { | ||
| statement.argument.properties.forEach((prop: any) => { | ||
| if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) { | ||
| const name = prop.key.name | ||
| if (!result.state[name] && !result.computed[name] && declaredFunctions.has(name)) { | ||
| const bodyCode = functionBodies[name] || `function ${name}(){}` | ||
| result.methods[name] = { type: 'function', value: bodyCode } | ||
| } else if (!result.state[name] && !result.methods[name] && !result.computed[name]) { | ||
| result.methods[name] = { type: 'function', value: `function ${name}(){}` } | ||
| } | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
Don’t invent methods for exposed non-function bindings in setup return
Returning count or any ref from setup() becomes a fake method stub. That’s incorrect and breaks DSL fidelity.
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
const name = prop.key.name
- if (!result.state[name] && !result.computed[name] && declaredFunctions.has(name)) {
+ if (declaredFunctions.has(name)) {
const bodyCode = functionBodies[name] || `function ${name}(){}`
result.methods[name] = { type: 'function', value: bodyCode }
- } else if (!result.state[name] && !result.methods[name] && !result.computed[name]) {
- result.methods[name] = { type: 'function', value: `function ${name}(){}` }
}
}Follow-up: once state extraction is generalized (previous comment), refs/reactives will already be present in result.state; unknown exposures should be left untouched rather than coerced into methods.
🤖 Prompt for AI Agents
In packages/vue-to-dsl/src/parsers/scriptParser.ts around lines 260 to 272 the
code currently invents method stubs for any name returned from setup() that
isn't in result.state or result.computed, producing fake methods for
refs/reactives; change the logic so you only add an entry to result.methods when
the returned name is a known function (e.g. declaredFunctions.has(name) or a
corresponding functionBodies[name] exists); remove the branch that
unconditionally creates a `function name(){}` stub for unknown exposures so
unknown/non-function bindings are left untouched.
| <style scoped> | ||
| body { | ||
| background-color: #eef0f5; | ||
| margin-bottom: 80px; | ||
| } | ||
| </style> |
There was a problem hiding this comment.
Scoped styles won't apply to body element.
The <style scoped> block targets body, which is outside the component's scope. Scoped styles only apply to elements within the component template.
Remove the scoped attribute or move body styles to a global stylesheet.
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/testcases/002_createVM/input/component.vue around
lines 406-411, the <style scoped> block is styling the global body element which
won't be affected because scoped styles only apply to elements inside the
component; either remove the scoped attribute so the body rules become global,
or move the body styles into a global stylesheet (e.g., main.css or a
root/global <style> without scoped) and keep component-specific styles scoped.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (10)
packages/vue-to-dsl/test/testcases/002_createVM/expected/schema.json (2)
61-67: Duplicate/colliding props: emit one canonical casing (prefer camelCase)Both
labelWidth/labelPositionandlabel-width/label-positionare emitted. Keep one form; recommend camelCase and dedupe by explicit precedence (kebab → camel). Apply across all similar blocks in this file.Example fix (apply pattern to all occurrences):
- "labelWidth": "80px", - "labelPosition": "top", - "label-position": "left ", - "label-width": "150px", + "labelWidth": "150px", + "labelPosition": "left",Also update the converter/generator to normalize prop casing and resolve conflicts deterministically (last-write-wins with kebab → camel precedence).
Also applies to: 199-205, 538-544, 667-673, 746-752, 907-913
118-122: Invalid CSS serialization: "[object Event]" leaked into style
"background-color: [object Event]"indicates a non-primitive slipped into CSS serialization. Guard during style stringify to only include string/number values; skip others. Update generator and fixtures.Minimal fixture correction:
- "style": "background-color: [object Event]; color: #8a8e99; font-size: 12px" + "style": "color: #8a8e99; font-size: 12px"Generator guard (pseudo-TypeScript):
for (const [k, v] of Object.entries(styleObj)) { if (typeof v !== 'string' && typeof v !== 'number') continue parts.push(`${toKebab(k)}: ${String(v)}`) }packages/vue-to-dsl/src/parsers/scriptParser.ts (2)
174-199: Generalize state extraction and reorder detection to prevent misclassificationCurrent logic only extracts when variable is literally
stateand runs after method detection, missingconst count = ref(1)and potentially misclassifying.Apply:
- function assignStateIfNamedState(name: string, init: any, result: any): boolean { - if (name !== 'state') return false - const initCode = getNodeValue(init) - if (isVueReactiveCall(init, 'reactive')) { - const firstArg = init.arguments && init.arguments[0] - if (t.isObjectExpression(firstArg)) { - firstArg.properties.forEach((prop: any) => { - if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) { - const propName = prop.key.name - const propValue = prop.value ? getNodeValue(prop.value) : undefined - result.state[propName] = { type: 'reactive', value: propValue } - } - }) - } else { - result.state[name] = { type: 'reactive', value: initCode } - } - return true - } - if (isVueReactiveCall(init, 'ref')) { - result.state[name] = { type: 'ref', value: initCode } - return true - } - // normal non-reactive assignment to state - result.state[name] = { type: 'normal', value: initCode } - return true - } + function assignStateFromVueCalls(name: string, init: any, result: any): boolean { + const initCode = getNodeValue(init) + // reactive(...) + if (isVueReactiveCall(init, 'reactive')) { + const firstArg = init.arguments?.[0] + if (t.isObjectExpression(firstArg)) { + firstArg.properties.forEach((prop: any) => { + if (t.isObjectProperty(prop) && (t.isIdentifier(prop.key) || t.isStringLiteral(prop.key))) { + const propName = t.isIdentifier(prop.key) ? prop.key.name : prop.key.value + const propValue = prop.value ? getNodeValue(prop.value) : undefined + result.state[propName] = { type: 'reactive', value: propValue } + } + }) + } else { + result.state[name] = { type: 'reactive', value: initCode } + } + return true + } + // ref(...) + if (isVueReactiveCall(init, 'ref')) { + result.state[name] = { type: 'ref', value: initCode } + return true + } + return false + } @@ - function handleVariableDeclarator(name: string, init: any, result: any, source: string) { - // 1) function-like assignments become methods - if (addMethodFromFunctionLike(name, init, result, source)) return - // 2) state-only extraction - if (assignStateIfNamedState(name, init, result)) return - // 3) computed regardless of name - if (assignComputedIfComputed(name, init, result, source)) return - // 4) otherwise ignored for state (per requirement), no-op - } + function handleVariableDeclarator(name: string, init: any, result: any, source: string) { + // 1) state/computed first to avoid misclassification + if (assignStateFromVueCalls(name, init, result)) return + if (assignComputedIfComputed(name, init, result, source)) return + // 2) then function-like → methods/lifecycle + if (addMethodFromFunctionLike(name, init, result, source)) return + }Also applies to: 214-222
260-272: Don’t invent methods for non-function bindings returned from setup()Returning refs/reactives currently creates fake method stubs. Only register a method when it’s a known declared function.
Apply:
- if (!result.state[name] && !result.computed[name] && declaredFunctions.has(name)) { + if (!result.state[name] && !result.computed[name] && declaredFunctions.has(name)) { const bodyCode = functionBodies[name] || `function ${name}(){}` result.methods[name] = { type: 'function', value: bodyCode } - } else if (!result.state[name] && !result.methods[name] && !result.computed[name]) { - result.methods[name] = { type: 'function', value: `function ${name}(){}` } }packages/vue-to-dsl/src/index.d.ts (5)
17-27: TemplateParser return type should be an array
Implementation returns TemplateSchema[].export interface TemplateParser { - parse(template: string, options?: any): TemplateSchema + parse(template: string, options?: any): TemplateSchema[] } export interface ScriptParser { parse(script: string, options?: any): ScriptSchema } export interface StyleParser { parse(style: string, options?: any): StyleSchema }
40-48: Expose parser soft-failure signal on ScriptSchemaInclude optional error field to mirror parser behavior.
export interface ScriptSchema { state?: Record<string, any> methods?: Record<string, any> computed?: Record<string, any> lifeCycles?: Record<string, any> imports?: ImportInfo[] props?: PropInfo[] emits?: string[] + error?: string }
69-81: Make PageSchema.path optionalGenerator doesn’t always set
path.export interface PageSchema { componentName: 'Page' fileName: string - path: string + path?: string meta?: Record<string, any> state?: Record<string, any> methods?: Record<string, any> computed?: Record<string, any> lifeCycles?: Record<string, any> props?: PropInfo[] css?: string children?: TemplateSchema[] }
83-88: Allow ConvertResult.schema to be null on failureMatches converter’s error path.
export interface ConvertResult { - schema: PageSchema + schema: PageSchema | null dependencies: string[] errors: string[] warnings: string[] }
132-137: Fix generateSchema signature to match implementationParameters are parsed artifacts, not raw strings.
export function generateSchema( - template: string, - script: string, - style?: string, + template: TemplateSchema[], + script: ScriptSchema, + style?: StyleSchema, options?: VueToSchemaOptions ): Promise<PageSchema>packages/vue-to-dsl/src/generator/index.ts (1)
102-116: Make component IDs deterministic (no Math.random)
Current IDs violate “stable references” goal and make snapshots flaky.Apply deterministic hashing from traversal path + componentName.
+import { createHash } from 'crypto' @@ -// Generate an 8-char id with lowercase letters and digits -function generateId(): string { - let s = '' - while (s.length < 8) s += Math.random().toString(36).slice(2) - return s.slice(0, 8) -} +// Generate a deterministic 8-char id from a stable input +function generateIdFrom(input: string): string { + return createHash('md5').update(input).digest('hex').slice(0, 8) +} @@ -// Recursively assign id to nodes with componentName -function assignComponentIds(node: any): void { - if (!node || typeof node !== 'object') return - if (typeof node.componentName === 'string') { - if (!node.id) node.id = generateId() - } - if (Array.isArray(node.children)) node.children.forEach(assignComponentIds) -} +// Recursively assign deterministic ids using preorder path +function assignComponentIds(node: any, path = '0'): void { + if (!node || typeof node !== 'object') return + if (typeof node.componentName === 'string' && !node.id) { + node.id = generateIdFrom(`${path}:${node.componentName}`) + } + if (Array.isArray(node.children)) { + node.children.forEach((child: any, idx: number) => assignComponentIds(child, `${path}.${idx}`)) + } +}
🧹 Nitpick comments (10)
packages/vue-to-dsl/test/sfc/converter.test.js (1)
82-105: Add tests for standalone ref and avoiding fake method stubsStrengthen coverage to (1) capture standalone
refas state and (2) ensure returned refs aren’t turned into method stubs.Append:
+ it('should capture standalone ref as state', async () => { + const vueCode = ` +<script setup> +import { ref } from 'vue' +const count = ref(1) +</script>` + const result = await converter.convertFromString(vueCode) + expect(result.errors).toHaveLength(0) + expect(result.schema.state.count).toBeDefined() + }) + + it('should NOT create fake methods for returned refs', async () => { + const vueCode = ` +<script setup> +import { ref } from 'vue' +const count = ref(1) +export default {} +</script>` + const result = await converter.convertFromString(vueCode) + expect(result.errors).toHaveLength(0) + expect(result.schema.methods.count).toBeUndefined() + })packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
347-355: Optional: parse Options API data() to extract initial stateCurrently sets a placeholder function string. Consider parsing the
data()return object to populateresult.statekeys for better fidelity.packages/vue-to-dsl/test/full/expected/schema.json (3)
1226-1246: Normalize v-model → modelValue consistentlyDSL currently mixes "modelValue" and "v-model". Use one convention (typically "modelValue" with events) to avoid renderer ambiguity.
If the runtime expects "modelValue", rewrite "v-model" props during generation.
Also applies to: 1279-1296, 1335-1352, 1380-1396, 1423-1430, 1650-1666, 1670-1686, 1756-1773, 1776-1783, 1864-1871, 1890-1897, 2025-2033
1899-1903: Inconsistent expression context in loopUses "this.state.dataDisk" while other bindings use "state.*". Pick one convention; recommend dropping "this." for consistency with other JSExpressions.
Apply during template transform when emitting loop.value.
674-676: Avoid external localhost URLs in test fixturesHard-coded http://localhost:9090 couples tests to an external service.
Mock or replace with inert placeholder and assert shape, not fetch result.
packages/vue-to-dsl/src/generator/index.ts (3)
118-131: Sanitizer is a no-op but comment claims sanitizationFunction returns strings unchanged; the name/comment mislead.
Either implement real sanitization (and skip css/JSFunction) or remove the pass and update the comment to avoid confusion.
2-11: convertToPlainValue: safer string de-quotingRegex
^['"].*['"]$is greedy and ignores escapes; can mangle edge cases.Prefer precise match on same-quote ends and basic escape handling:
- if (/^['"].*['"]$/.test(trimmed)) return trimmed.slice(1, -1) + if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) { + const s = trimmed.slice(1, -1) + return s.replace(/\\(["'\\nrtb])/g, (_m, c) => ({ n: '\n', r: '\r', t: '\t', b: '\b', '"': '"', "'": "'", '\\': '\\' }[c] ?? c)) + }
157-170: Optional: enrich app meta and stampFixture includes generatedAt/generator fields. Consider populating them here to centralize behavior.
E.g., set
meta.generatedAt = new Date().toISOString()andmeta.generatorwith package name; gate behind option to keep tests deterministic.packages/vue-to-dsl/src/index.d.ts (2)
2-15: Options type lacks fields used by implementationAdd common generation fields to VueToSchemaOptions.
export interface VueToSchemaOptions { componentMap?: Record<string, string> preserveComments?: boolean strictMode?: boolean customParsers?: { template?: TemplateParser script?: ScriptParser style?: StyleParser } + fileName?: string + path?: string + title?: string + description?: string }
112-118: Broaden parseVueFile/parseSFC return metadataExpose langs, style/custom blocks, and warnings for consumers.
export function parseVueFile(filePath: string): Promise<{ template?: string script?: string style?: string scriptSetup?: string + templateLang?: string + scriptLang?: string + scriptSetupLang?: string + styleBlocks?: Array<{ content: string; lang?: string; scoped?: boolean; module?: boolean }> + customBlocks?: Array<{ type: string; content: string; attrs: Record<string, any> }> + warnings?: any[] }> export function parseSFC(vueCode: string): { template?: string script?: string style?: string scriptSetup?: string + templateLang?: string + scriptLang?: string + scriptSetupLang?: string + styleBlocks?: Array<{ content: string; lang?: string; scoped?: boolean; module?: boolean }> + customBlocks?: Array<{ type: string; content: string; attrs: Record<string, any> }> + warnings?: any[] }Also applies to: 122-128
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
packages/vue-to-dsl/cli.ts(1 hunks)packages/vue-to-dsl/src/generator/index.ts(1 hunks)packages/vue-to-dsl/src/index.d.ts(1 hunks)packages/vue-to-dsl/src/parsers/scriptParser.ts(1 hunks)packages/vue-to-dsl/src/types/index.js(1 hunks)packages/vue-to-dsl/test/full/expected/schema.from-zip.json(1 hunks)packages/vue-to-dsl/test/full/expected/schema.json(1 hunks)packages/vue-to-dsl/test/sfc/converter.test.js(1 hunks)packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/002_createVM/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/003_login/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/005_survey/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/006_lifecycle/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/006_lifecycle/input/component.vue(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json
- packages/vue-to-dsl/test/full/expected/schema.from-zip.json
- packages/vue-to-dsl/cli.ts
- packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json
- packages/vue-to-dsl/test/testcases/003_login/expected/schema.json
- packages/vue-to-dsl/test/testcases/005_survey/expected/schema.json
- packages/vue-to-dsl/src/types/index.js
🧰 Additional context used
🧬 Code graph analysis (4)
packages/vue-to-dsl/test/sfc/converter.test.js (2)
packages/vue-to-dsl/test/testcases/index.test.js (1)
converter(8-8)packages/vue-to-dsl/src/converter.ts (1)
VueToDslConverter(34-822)
packages/vue-to-dsl/src/generator/index.ts (2)
packages/vue-to-dsl/src/index.ts (2)
generateSchema(5-5)generateAppSchema(5-5)packages/vue-to-dsl/src/constants.ts (1)
defaultComponentsMap(18-217)
packages/vue-to-dsl/src/index.d.ts (2)
packages/vue-to-dsl/src/generator/index.ts (1)
generateSchema(133-155)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(460-488)
packages/vue-to-dsl/src/parsers/scriptParser.ts (2)
packages/vue-to-dsl/src/index.ts (1)
parseScript(6-6)packages/vue-to-dsl/src/parsers/index.ts (1)
parseScript(2-2)
🔇 Additional comments (3)
packages/vue-to-dsl/test/testcases/006_lifecycle/input/component.vue (1)
5-48: LGTM for lifecycle fixtureCovers the major hooks cleanly for the converter to map into DSL. No changes needed.
packages/vue-to-dsl/test/testcases/006_lifecycle/expected/schema.json (1)
10-43: Confirm canonical value type for lifeCyclesParser writes
type: 'lifecycle'while expected usestype: 'JSFunction'. Ensure generator consistently normalizes to a single canonical type to avoid fixture drift.packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
124-131: Align lifecycle entry type with downstream schema
setLifecycleEntryusestype: 'lifecycle'. If DSL canonical type isJSFunction, update here or in the generator to maintain consistency.
| /** | ||
| * 解析模板 | ||
| */ | ||
| export function parseTemplate(template: string): TemplateSchema[] | ||
|
|
||
| /** | ||
| * 解析脚本 | ||
| */ | ||
| export function parseScript(script: string): ScriptSchema | ||
|
|
||
| /** | ||
| * 解析样式 | ||
| */ | ||
| export function parseStyle(style: string): StyleSchema | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
Export generateAppSchema in the public API
Index.ts re-exports it; add typing here.
export function parseStyle(style: string): StyleSchema
+
+/**
+ * 生成应用级 DSL Schema(App)
+ */
+export function generateAppSchema(pageSchemas: PageSchema[], options?: any): any🏁 Script executed:
#!/bin/bash
# Ensure index.ts exports generateAppSchema and consumers import it
rg -nP 'generateAppSchema' -C2 packages/vue-to-dsl/src/index.ts packages/vue-to-dsl/**/cli.* packages/vue-to-dsl/**/test/**.*Length of output: 575
The search results are about generic .d.ts generation, not the specific codebase. Let me verify whether generateAppSchema is already typed in index.d.ts.
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for generateAppSchema in index.d.ts file
rg -n 'generateAppSchema' packages/vue-to-dsl/src/index.d.ts
Length of output: 74
Let me search for all .d.ts files and locate where types are actually defined:
---
🏁 Script executed:
```shell
#!/bin/bash
# Find all .d.ts files in packages/vue-to-dsl
fd -e d.ts packages/vue-to-dsl/
# Also search for PageSchema definition
rg -n 'PageSchema' packages/vue-to-dsl/src/ -g '*.ts' -C1
Length of output: 939
Good! Now let me verify the actual implementation of generateAppSchema and check the full context of index.d.ts:
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for generateAppSchema implementation
rg -n 'generateAppSchema' packages/vue-to-dsl/src/generator.ts -A5
# Get the full export section from index.d.ts
cat packages/vue-to-dsl/src/index.d.ts | tail -30
Length of output: 585
Let me search for where the generator module is located and find the generateAppSchema signature:
---
🏁 Script executed:
```shell
#!/bin/bash
# Find generator files
fd -e ts -e js . packages/vue-to-dsl/src/ | grep -i generator
# Search for the generateAppSchema function definition
rg -n 'export.*generateAppSchema|function generateAppSchema' packages/vue-to-dsl/src/ -A3
Length of output: 642
Add generateAppSchema typing to index.d.ts public API
The function is exported from index.ts but lacks type definitions. Add the missing export:
export function parseStyle(style: string): StyleSchema
+
+ /**
+ * 生成应用级 DSL Schema(App)
+ */
+ export function generateAppSchema(pageSchemas: PageSchema[], options?: any): any📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * 解析模板 | |
| */ | |
| export function parseTemplate(template: string): TemplateSchema[] | |
| /** | |
| * 解析脚本 | |
| */ | |
| export function parseScript(script: string): ScriptSchema | |
| /** | |
| * 解析样式 | |
| */ | |
| export function parseStyle(style: string): StyleSchema | |
| } | |
| /** | |
| * 解析模板 | |
| */ | |
| export function parseTemplate(template: string): TemplateSchema[] | |
| /** | |
| * 解析脚本 | |
| */ | |
| export function parseScript(script: string): ScriptSchema | |
| /** | |
| * 解析样式 | |
| */ | |
| export function parseStyle(style: string): StyleSchema | |
| /** | |
| * 生成应用级 DSL Schema(App) | |
| */ | |
| export function generateAppSchema(pageSchemas: PageSchema[], options?: any): any | |
| } |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/src/index.d.ts around lines 139 to 153, add the missing
public typing for the exported generateAppSchema function so the declaration
file matches index.ts; declare and export a function signature such as export
function generateAppSchema(source: string): AppSchema (or the correct
parameter/return types used in index.ts) and ensure the AppSchema type is
imported or declared and exported from the d.ts file so consumers can use the
typed API.
| "componentName": "span", | ||
| "props": { | ||
| "style": "background-color: [object Event]; color: #8a8e99; font-size: 12px" | ||
| }, | ||
| "children": [ | ||
| { | ||
| "componentName": "Text", | ||
| "props": { | ||
| "text": "温馨提示:页面左上角切换区域" | ||
| }, | ||
| "id": "w6zeqr7n" | ||
| } | ||
| ], |
There was a problem hiding this comment.
Suspicious inline style contains “[object Event]”
The style value suggests an event object was stringified during conversion. Likely a bug in style binding serialization.
Please ensure style props are either plain strings or JSExpression values; avoid passing event objects through string coercion. Example upstream fix: when normalizing style bindings, only stringify literals; wrap dynamic expressions as { type: 'JSExpression', value: '<expr>' }.
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/expected/schema.json around lines 1184 to 1196,
the inline style for the span was stringified to include “[object Event]”
indicating an event object was coerced into the style string; update the style
normalization so it does not stringify Event/Function objects — detect
non-primitive style values and instead emit either a plain CSS string for static
styles or a JSExpression wrapper for dynamic expressions (e.g., { type:
'JSExpression', value: '<expr>' }); ensure any object values that are not valid
CSS maps are filtered out or converted to expression nodes and add a unit test
to assert style props are either strings or JSExpression objects, not
stringified events.
| { | ||
| "componentName": "TinyCarouselItem", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "CarouselItem", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyCheckboxButton", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "CheckboxButton", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyTree", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Tree", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyPopover", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Popover", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyTooltip", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Tooltip", | ||
| "destructuring": true, | ||
| "version": "3.2.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyCol", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Col", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyDropdownItem", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "DropdownItem", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyPager", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Pager", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyPlusAccessdeclined", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "AccessDeclined", | ||
| "destructuring": true, | ||
| "version": "3.4.1" | ||
| }, | ||
| { | ||
| "componentName": "TinyPlusFrozenPage", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "FrozenPage", | ||
| "destructuring": true, | ||
| "version": "3.4.1" | ||
| }, | ||
| { | ||
| "componentName": "TinyPlusNonSupportRegion", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "NonSupportRegion", | ||
| "destructuring": true, | ||
| "version": "3.4.1" | ||
| }, | ||
| { | ||
| "componentName": "TinyPlusBeta", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Beta", | ||
| "destructuring": true, | ||
| "version": "3.4.1" | ||
| }, | ||
| { | ||
| "componentName": "TinySearch", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Search", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyRow", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Row", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyFormItem", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "FormItem", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyAlert", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Alert", | ||
| "destructuring": true, | ||
| "version": "3.2.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyInput", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Input", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyTabs", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Tabs", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyDropdownMenu", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "DropdownMenu", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyDialogBox", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "DialogBox", | ||
| "destructuring": true, | ||
| "version": "3.2.0" | ||
| }, | ||
| { | ||
| "componentName": "TinySwitch", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Switch", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyTimeLine", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "TimeLine", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyTabItem", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "TabItem", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyRadio", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Radio", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyForm", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Form", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyGrid", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Grid", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyNumeric", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Numeric", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyCheckboxGroup", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "CheckboxGroup", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinySelect", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Select", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyButtonGroup", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "ButtonGroup", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyButton", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Button", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyCarousel", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Carousel", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyPopeditor", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Popeditor", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyDatePicker", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "DatePicker", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyDropdown", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "Dropdown", | ||
| "destructuring": true, | ||
| "version": "0.1.20" | ||
| }, | ||
| { | ||
| "componentName": "TinyChartHistogram", | ||
| "package": "@opentiny/vue", | ||
| "exportName": "ChartHistogram", | ||
| "destructuring": true, | ||
| "version": "3.24.0" | ||
| }, | ||
| { | ||
| "componentName": "PortalHome", | ||
| "main": "common/components/home", | ||
| "destructuring": false, | ||
| "version": "1.0.0" | ||
| }, | ||
| { | ||
| "componentName": "PreviewBlock1", | ||
| "main": "preview", | ||
| "destructuring": false, | ||
| "version": "1.0.0" | ||
| }, | ||
| { | ||
| "componentName": "PortalHeader", | ||
| "main": "common", | ||
| "destructuring": false, | ||
| "version": "1.0.0" | ||
| }, | ||
| { | ||
| "componentName": "PortalBlock", | ||
| "main": "portal", | ||
| "destructuring": false, | ||
| "version": "1.0.0" | ||
| }, | ||
| { | ||
| "componentName": "PortalPermissionBlock", | ||
| "main": "", | ||
| "destructuring": false, | ||
| "version": "1.0.0" | ||
| }, | ||
| { | ||
| "componentName": "TinyCard", | ||
| "exportName": "Card", | ||
| "package": "@opentiny/vue", | ||
| "version": "^3.10.0", | ||
| "destructuring": true | ||
| } | ||
| ] |
There was a problem hiding this comment.
🧩 Analysis chain
Icon component used but not declared in componentsMap
Schema references componentName "Icon" (e.g., Lines 1834–1841, 1912–1919) but componentsMap lacks a corresponding entry. Missing mapping will break runtime resolution.
Add an "Icon" mapping (or alias) to componentsMap or defaultComponentsMap.
🏁 Script executed:
#!/bin/bash
# Check for Icon usage vs mapping
rg -nP '"componentName":\s*"Icon"' -C2 packages/vue-to-dsl/test/full/expected/schema.json
rg -nP '"componentName":\s*"Icon"' -C2
rg -nP '"componentName":\s*"Icon"|Icon' -C2 packages/vue-to-dsl/src/constants.tsLength of output: 211008
Icon component missing from componentsMap
The Icon component is referenced in the schema (lines 1834, 1912) but has no corresponding entry in the componentsMap (lines 2194-2483). This will cause runtime resolution failures.
Add an Icon mapping entry following the existing pattern:
{
"componentName": "Icon",
"package": "@opentiny/vue",
"exportName": "Icon",
"destructuring": true,
"version": "<appropriate_version>"
}🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/full/expected/schema.json around lines 2194 to 2483,
the componentsMap array is missing an entry for the Icon component referenced
earlier, causing runtime resolution failures; add a new object to the array
matching the existing entries, e.g. with "componentName": "Icon", "package":
"@opentiny/vue", "exportName": "Icon", "destructuring": true and set "version"
to the appropriate package version (consistent with other @opentiny/vue
components, e.g. "3.24.0"), placed in the same format as the surrounding
component entries.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
packages/vue-to-dsl/src/parsers/templateParser.ts (3)
152-154: Standardize v-if/v-show to JSExpression objects (consistent schema shape)Elsewhere expressions are wrapped; here they’re raw strings. Wrap and prefix for consistency.
Apply:
- schema.condition = prop.exp ? prop.exp.content : 'true' + schema.condition = { type: 'JSExpression', value: ensureThisPrefix(prop.exp ? prop.exp.content : 'true') } ... - schema.props['v-show'] = prop.exp ? prop.exp.content : 'true' + schema.props['v-show'] = { type: 'JSExpression', value: ensureThisPrefix(prop.exp ? prop.exp.content : 'true') } ... - schema.props[`v-${directiveName}`] = prop.exp ? prop.exp.content : 'true' + { + const raw = prop.exp ? prop.exp.content : 'true' + const parsed = parseLiteralExpression(raw) + schema.props[`v-${directiveName}`] = parsed.ok + ? parsed.value + : { type: 'JSExpression', value: ensureThisPrefix(raw) } + }Also applies to: 166-168, 204-206
160-164: Fix invalid[^]regex; avoid linter error and overmatchingReplace negated empty character classes with a safer approach. Simpler and clearer: slice after the last " in " or " of ".
Apply:
- const match = - exp.match(/^[^]*?(?:\)|\S)\s+(?:in|of)\s+([^]+)$/) || exp.match(/^(?:[^]+?)\s+(?:in|of)\s+([^]+)$/) - const src = (match ? match[1] : exp).trim() + const inIdx = exp.lastIndexOf(' in ') + const ofIdx = exp.lastIndexOf(' of ') + const idx = Math.max(inIdx, ofIdx) + const src = (idx >= 0 ? exp.slice(idx + 4) : exp).trim() schema.loop = { type: 'JSExpression', value: ensureThisPrefix(src) }
240-245: Icon normalization drops existing props/handlersReassigning props loses classes, aria, and events parsed earlier. Preserve and extend instead.
Apply:
- const styleVal = schema.props && typeof schema.props.style === 'string' ? schema.props.style : undefined - // Rebuild props: keep style if present; set name; drop other raw attributes like fill - schema.componentName = 'Icon' - schema.props = {} - if (styleVal) schema.props.style = styleVal - schema.props.name = iconName + const prev = { ...(schema.props || {}) } + schema.componentName = 'Icon' + schema.props = { ...prev, name: iconName } + // If needed, selectively strip SVG-only attrs here instead of nuking all: + // const { fill, stroke, ...rest } = schema.props; schema.props = { ...rest, name: iconName }
🧹 Nitpick comments (4)
packages/vue-to-dsl/test/testcases/005_survey/expected/schema.json (1)
199-207: Unify button labeling style; verify Radio mapping
- TinyButton here uses props.text, while other fixtures use Text children. Pick one convention to avoid UI divergence.
- Also confirm TinyRadio per-item v-model matches Tiny UI API (vs. a TinyRadioGroup). If group is required, generator should emit group + options.
packages/vue-to-dsl/src/parsers/templateParser.ts (1)
128-144: Handlenullliteral in parseLiteralExpressionTiny but useful: treat "null" as a literal to reduce JSExpression fallbacks.
Apply:
if (trimmed === 'true') return { ok: true, value: true } if (trimmed === 'false') return { ok: true, value: false } + if (trimmed === 'null') return { ok: true, value: null }packages/vue-to-dsl/test/testcases/index.test.js (2)
10-11: Filter only directories for test casesAvoid accidental files matching the pattern.
Apply:
- const cases = fs.readdirSync(baseDir).filter((name) => /\d+_/.test(name)) + const cases = fs + .readdirSync(baseDir) + .filter((name) => /\d+_/.test(name) && fs.statSync(path.join(baseDir, name)).isDirectory())
85-95: Assert no warnings in passing cases (or print for diagnostics)Catches silent degradations without failing error count.
Apply:
} else { expect(result.errors).toHaveLength(0) + expect(result.warnings).toHaveLength(0) expect(result.schema).toBeDefined()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
packages/vue-to-dsl/src/parsers/templateParser.ts(1 hunks)packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/003_login/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/005_survey/expected/schema.json(1 hunks)packages/vue-to-dsl/test/testcases/index.test.js(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/vue-to-dsl/test/testcases/001_simple/expected/schema.json
🧰 Additional context used
🧬 Code graph analysis (2)
packages/vue-to-dsl/src/parsers/templateParser.ts (2)
packages/vue-to-dsl/src/index.ts (1)
parseTemplate(6-6)packages/vue-to-dsl/src/parsers/index.ts (1)
parseTemplate(1-1)
packages/vue-to-dsl/test/testcases/index.test.js (2)
packages/vue-to-dsl/test/sfc/converter.test.js (1)
converter(5-5)packages/vue-to-dsl/src/converter.ts (1)
VueToDslConverter(34-822)
🪛 Biome (2.1.2)
packages/vue-to-dsl/src/parsers/templateParser.ts
[error] 161-161: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 161-161: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 161-161: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
[error] 161-161: The regular expression includes this negated empty character class.
Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.
(lint/correctness/noEmptyCharacterClassInRegex)
🔇 Additional comments (1)
packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json (1)
115-407: Fixture looks consistent with conversion rulesSchema structure, bindings, and styles align with parser/generator patterns.
| "onSubmit": { | ||
| "type": "JSFunction", | ||
| "value": "function onSubmit() {\n if (!state.form.username || !state.form.password) return\n console.log('login', state.form)\n}" | ||
| }, | ||
| "onReset": { | ||
| "type": "JSFunction", | ||
| "value": "function onReset() {\n state.form.username = ''\n state.form.password = ''\n}" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
Use this.state for method bodies (align with template expressions and other fixtures)
Methods reference state directly; elsewhere bindings use this.state. Standardize to avoid runtime scope mismatches.
Example fix:
- "value": "function onSubmit() {\n if (!state.form.username || !state.form.password) return\n console.log('login', state.form)\n}"
+ "value": "function onSubmit() {\n if (!this.state.form.username || !this.state.form.password) return\n console.log('login', this.state.form)\n}"And similarly for onReset.
I'll verify this review comment by examining the repository structure and checking patterns across fixtures and templates.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find the test directory structure
find packages/vue-to-dsl/test/testcases -type d -maxdepth 1 | head -20
Length of output: 317
🏁 Script executed:
#!/bin/bash
# Look for schema.json files with JSFunction entries
find packages/vue-to-dsl/test/testcases -name "schema.json" -type f | head -10Length of output: 408
🏁 Script executed:
#!/bin/bash
# Search for JSFunction patterns in schema files to see how state is referenced
rg -n "JSFunction" packages/vue-to-dsl/test/testcases -A 3 --type json | head -40Length of output: 4248
🏁 Script executed:
#!/bin/bash
# Look for template files in test cases
find packages/vue-to-dsl/test/testcases -name "*.vue" -o -name "*.html" | head -10Length of output: 402
🏁 Script executed:
#!/bin/bash
# Search for 'this.state' vs 'state' patterns in expected schemas
rg -n "this\.state|\"state\." packages/vue-to-dsl/test/testcases --type jsonLength of output: 3488
---
🏁 Script executed:
```shell
#!/bin/bash
# Get the exact content of 003_login schema.json to verify the issue
cat packages/vue-to-dsl/test/testcases/003_login/expected/schema.json
Length of output: 9174
🏁 Script executed:
#!/bin/bash
# Get onSearch method from 004_dashboard
rg -n "onSearch" packages/vue-to-dsl/test/testcases/004_dashboard/expected/schema.json -A 3 -B 1Length of output: 452
Fix method bodies in 003_login to use this.state consistently with template expressions
The fixture has a scope mismatch: methods reference state directly while template bindings use this.state. Template expressions throughout the fixture (and other fixtures like 005_survey) consistently use this.state. Update both onSubmit and onReset methods to use this.state.form instead of bare state.form to align with the template binding pattern.
"onSubmit": {
"type": "JSFunction",
- "value": "function onSubmit() {\n if (!state.form.username || !state.form.password) return\n console.log('login', state.form)\n}"
+ "value": "function onSubmit() {\n if (!this.state.form.username || !this.state.form.password) return\n console.log('login', this.state.form)\n}"
},
"onReset": {
"type": "JSFunction",
- "value": "function onReset() {\n state.form.username = ''\n state.form.password = ''\n}"
+ "value": "function onReset() {\n this.state.form.username = ''\n this.state.form.password = ''\n}"
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "onSubmit": { | |
| "type": "JSFunction", | |
| "value": "function onSubmit() {\n if (!state.form.username || !state.form.password) return\n console.log('login', state.form)\n}" | |
| }, | |
| "onReset": { | |
| "type": "JSFunction", | |
| "value": "function onReset() {\n state.form.username = ''\n state.form.password = ''\n}" | |
| } | |
| "onSubmit": { | |
| "type": "JSFunction", | |
| "value": "function onSubmit() {\n if (!this.state.form.username || !this.state.form.password) return\n console.log('login', this.state.form)\n}" | |
| }, | |
| "onReset": { | |
| "type": "JSFunction", | |
| "value": "function onReset() {\n this.state.form.username = ''\n this.state.form.password = ''\n}" | |
| } |
🤖 Prompt for AI Agents
In packages/vue-to-dsl/test/testcases/003_login/expected/schema.json around
lines 14 to 21, the method bodies use the free identifier `state` while template
bindings use `this.state`; update both onSubmit and onReset to reference
`this.state.form` (and `this.state` where applicable) instead of `state.form` so
the methods access the same scoped data as the template expressions, keeping the
rest of the function logic unchanged.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (17)
packages/vue-to-dsl/src/converter.ts (8)
79-85: Pass options to custom script parser for API consistency.Align with the default parser by passing the same options object including
isSetup.Apply this diff:
scriptSchema = this.options.customParsers?.script - ? this.options.customParsers.script.parse(scriptContent) + ? this.options.customParsers.script.parse(scriptContent, { + isSetup: !!sfcResult.scriptSetup, + ...(this.options as any) + }) : parseScript(scriptContent, { isSetup: !!sfcResult.scriptSetup, ...(this.options as any) })
104-107: Pass options to custom style parser for consistency.Align the custom parser call with the default parser signature.
Apply this diff:
styleSchema = this.options.customParsers?.style - ? this.options.customParsers.style.parse(sfcResult.style) + ? this.options.customParsers.style.parse(sfcResult.style, this.options as any) : parseStyle(sfcResult.style, this.options as any)
113-119: Avoid mutating instance options; pass a derived object to generator.Mutating
this.options.fileNameaffects subsequent calls. Create a derived options object instead.Apply this diff:
- // Set fileName in options for schema generation - if (fileName) { - this.options.fileName = fileName.replace(/\.vue$/i, '') - } - - const schema = await generateSchema(templateSchema, scriptSchema, styleSchema, this.options as any) + const genOptions = { + ...(this.options as any), + fileName: fileName ? fileName.replace(/\.vue$/i, '') : (this.options as any).fileName + } + const schema = await generateSchema(templateSchema, scriptSchema, styleSchema, genOptions)
552-591: Ensure temp directory cleanup with finally block.The temp directory can leak if conversion throws. Wrap in try/finally for guaranteed cleanup.
Apply this diff:
- // 3) Delegate to convertAppDirectory - const schema = await this.convertAppDirectory(appRoot) - return schema + // 3) Delegate to convertAppDirectory with cleanup + let schema: any + try { + schema = await this.convertAppDirectory(appRoot) + } finally { + await fs.rm(tmpBase, { recursive: true, force: true }).catch(() => {}) + } + return schema
12-27: Type customParsers to accept options parameter.The custom parser interface should align with the default parsers which accept options. This allows plugin authors to receive configuration.
Apply this diff:
customParsers?: { - template?: { parse: (code: string) => any } - script?: { parse: (code: string) => any } - style?: { parse: (code: string) => any } + template?: { parse: (code: string, options?: any) => any | any[] } + script?: { parse: (code: string, options?: any) => any } + style?: { parse: (code: string, options?: any) => any } }
67-73: Pass options to custom template parser and normalize return value to array.Custom parsers should receive options for consistency, and the template parser may return a non-array.
Apply this diff:
templateSchema = this.options.customParsers?.template - ? this.options.customParsers.template.parse(sfcResult.template) + ? this.options.customParsers.template.parse(sfcResult.template, this.options) : parseTemplate(sfcResult.template, this.options as any) + if (!Array.isArray(templateSchema)) { + templateSchema = templateSchema ? [templateSchema] : [] + }
284-289: Critical: Replace Function() with safe parsing to prevent code execution.Using
Function(...)()on user-supplied code from app directories or zip files is a severe security risk. Use a safe parser instead.Based on learnings
556-574: Zip Slip: Sanitize zip paths to prevent directory traversal.Malicious zip entries with
../can write outside the temp directory.Apply this diff:
- const outPath = path.join(tmpBase, relPath) + const destPath = path.resolve(tmpBase, relPath) + if (!destPath.startsWith(tmpBase + path.sep) && destPath !== tmpBase) { + return // block path traversal + } if (file.dir) { - writeTasks.push(fs.mkdir(outPath, { recursive: true })) + writeTasks.push(fs.mkdir(destPath, { recursive: true })) } else { fileEntries.push(relPath) writeTasks.push( (async () => { - await fs.mkdir(path.dirname(outPath), { recursive: true }) + await fs.mkdir(path.dirname(destPath), { recursive: true }) const content = await file.async('nodebuffer') - await fs.writeFile(outPath, content) + await fs.writeFile(destPath, content) })() ) }packages/vue-to-dsl/src/index.d.ts (8)
42-50: ScriptSchema should include optional error field.The parser returns
{ error }on parse failures, but the type doesn't reflect this.Apply this diff:
export interface ScriptSchema { state?: Record<string, any> methods?: Record<string, any> computed?: Record<string, any> lifeCycles?: Record<string, any> imports?: ImportInfo[] props?: PropInfo[] emits?: string[] + error?: string }
141-154: Add options parameter to parser function signatures.All parser implementations accept an optional
optionsparameter that's missing from the declarations.Apply this diff:
- export function parseTemplate(template: string): TemplateSchema[] + export function parseTemplate(template: string, options?: any): TemplateSchema[] - export function parseScript(script: string): ScriptSchema + export function parseScript(script: string, options?: any): ScriptSchema - export function parseStyle(style: string): StyleSchema + export function parseStyle(style: string, options?: any): StyleSchema
141-155: Add missing generateAppSchema export to public API.The function is exported from
index.tsbut lacks type declarations here.Add after line 154:
export function parseStyle(style: string): StyleSchema + + /** + * 生成应用级 DSL Schema(App) + */ + export function generateAppSchema(pageSchemas: PageSchema[], options?: any): any }
2-17: VueToSchemaOptions is missing fields and parser signatures are incomplete.The interface lacks
fileName,path,title, anddescriptionused by the implementation, and parser types don't accept options.Apply this diff:
export interface VueToSchemaOptions { // 组件映射配置 componentMap?: Record<string, string> // 是否保留注释 preserveComments?: boolean // 是否严格模式 strictMode?: boolean + // 控制是否输出 computed 字段(默认 false) + computed_flag?: boolean // 自定义解析器 customParsers?: { template?: TemplateParser script?: ScriptParser style?: StyleParser } + // 生成参数 + fileName?: string + path?: string + title?: string + description?: string }
19-21: TemplateParser should return an array to match implementation.The parser returns
TemplateSchema[]but the type declares a single object.Apply this diff:
export interface TemplateParser { - parse(template: string, options?: any): TemplateSchema + parse(template: string, options?: any): TemplateSchema[] }
71-83: Make PageSchema.path optional to match generator output.The generator doesn't guarantee a
pathfield, so requiring it causes type errors.Apply this diff:
export interface PageSchema { componentName: 'Page' fileName: string - path: string + path?: string meta?: Record<string, any>
85-90: ConvertResult.schema can be null on conversion failure.The implementation returns
{ schema: null }on errors, but the type doesn't allow null.Apply this diff:
export interface ConvertResult { - schema: PageSchema + schema: PageSchema | null dependencies: string[] errors: string[] warnings: string[] }
134-139: Fix generateSchema signature to accept parsed schemas.The implementation accepts
TemplateSchema[],ScriptSchema, andStyleSchema, not strings.Apply this diff:
export function generateSchema( - template: string, - script: string, - style?: string, + template: TemplateSchema[], + script: ScriptSchema, + style?: StyleSchema, options?: VueToSchemaOptions ): Promise<PageSchema>packages/vue-to-dsl/test/full/index.test.js (1)
6-10: Define __dirname in ESM/Vitest to avoid ReferenceError.Using
__dirnamewith import syntax will fail under ESM. Define it fromimport.meta.urlbefore use.Apply this diff:
import { describe, it, expect } from 'vitest' import { VueToDslConverter } from '../../src/converter' import fs from 'fs' import path from 'path' +import { fileURLToPath } from 'node:url' +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) const fullDir = __dirname const inputDir = path.join(fullDir, 'input')
🧹 Nitpick comments (1)
packages/vue-to-dsl/src/converter.ts (1)
606-640: Consider validating gitignore patterns to prevent ReDoS.Building regex from gitignore content could be exploited with malicious patterns. Consider adding pattern validation or using a tested gitignore library.
For production use, consider using a library like
ignore(npm) which handles gitignore patterns safely, or add regex complexity validation before constructing patterns.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
packages/vue-to-dsl/README.md(1 hunks)packages/vue-to-dsl/cli.ts(1 hunks)packages/vue-to-dsl/src/converter.ts(1 hunks)packages/vue-to-dsl/src/generator/index.ts(1 hunks)packages/vue-to-dsl/src/index.d.ts(1 hunks)packages/vue-to-dsl/test/full/index.test.js(1 hunks)packages/vue-to-dsl/test/sfc/converter.test.js(1 hunks)packages/vue-to-dsl/test/testcases/index.test.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/vue-to-dsl/test/testcases/index.test.js
- packages/vue-to-dsl/src/generator/index.ts
- packages/vue-to-dsl/cli.ts
- packages/vue-to-dsl/test/sfc/converter.test.js
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-01-14T06:50:21.158Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/data-function/parser.ts:191-195
Timestamp: 2025-01-14T06:50:21.158Z
Learning: The `newFn` function in `packages/canvas/render/src/data-function/parser.ts` has a known security issue with evaluating untrusted code through `data.value`. This was identified during code review but intentionally deferred as the original implementation was kept during code refactoring.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-01-14T06:58:38.661Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/data-utils.ts:8-12
Timestamp: 2025-01-14T06:58:38.661Z
Learning: The use of Function constructor in `packages/canvas/render/src/data-utils.ts` is part of migrated code and was intentionally kept as-is during migration, despite potential security concerns.
Applied to files:
packages/vue-to-dsl/src/converter.ts
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
PR: opentiny/tiny-engine#1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.
Applied to files:
packages/vue-to-dsl/test/full/index.test.js
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/vue-to-dsl/README.md
🧬 Code graph analysis (3)
packages/vue-to-dsl/src/converter.ts (6)
packages/vue-to-dsl/src/index.d.ts (3)
VueToSchemaOptions(2-17)ConvertResult(85-90)VueToDslConverter(92-109)packages/vue-to-dsl/src/constants.ts (1)
defaultComponentMap(1-16)packages/vue-to-dsl/src/parser/index.ts (1)
parseSFC(4-41)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(460-488)packages/vue-to-dsl/src/parsers/styleParser.ts (1)
parseStyle(1-6)packages/vue-to-dsl/src/generator/index.ts (2)
generateSchema(133-158)generateAppSchema(160-173)
packages/vue-to-dsl/src/index.d.ts (3)
packages/vue-to-dsl/src/converter.ts (3)
VueToSchemaOptions(12-27)ConvertResult(29-34)VueToDslConverter(36-825)packages/vue-to-dsl/src/generator/index.ts (1)
generateSchema(133-158)packages/vue-to-dsl/src/parsers/scriptParser.ts (1)
parseScript(460-488)
packages/vue-to-dsl/test/full/index.test.js (2)
packages/vue-to-dsl/test/sfc/converter.test.js (1)
converter(5-5)packages/vue-to-dsl/test/testcases/index.test.js (1)
converter(8-8)
🪛 ast-grep (0.39.6)
packages/vue-to-dsl/src/converter.ts
[warning] 624-624: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexString})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
[warning] 627-627: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexString}(/.*)?$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🪛 LanguageTool
packages/vue-to-dsl/README.md
[uncategorized] ~172-~172: 您的意思是“"不"齐”?
Context: ...定(页面级) - 根节点 componentName: 'Page',自动补齐 id(8 位字母数字) - state/methods/`com...
(BU)
English | 简体中文
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Background and solution
What is the current behavior?
项目缺少从 Vue 源码到 Tiny Engine DSL 的转换能力。
What is the new behavior?
本 PR 新增 vue-to-dsl 插件并围绕解析与生成流程进行增强,核心包括:
<script setup>、完整Vue项目转换等)。Does this PR introduce a breaking change?
Summary by CodeRabbit
New Features
Documentation
Tests