-
Couldn't load subscription status.
- Fork 434
feat: introduce react-generator plugin #1593
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ospp-2025/source-to-dsl
Are you sure you want to change the base?
feat: introduce react-generator plugin #1593
Conversation
WalkthroughAdds a new packages/react-generator module: a plugin-driven code generator for converting low-code schemas into React projects, including generators, plugins, templates, utilities, TypeScript declarations, build/test tooling, and extensive tests and fixtures. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Caller
participant GA as generateApp(config)
participant CG as CodeGenerator
participant PS as parseSchema (transformStart)
participant PL as Plugins (transform)
participant PF as formatCode (transformEnd)
Dev->>GA: call generateApp(config)
GA->>CG: instantiate CodeGenerator with phases & context
Dev->>CG: call generate(schema)
CG->>PS: run transformStart plugins (e.g. parseSchema)
note right of PS #DDEEFF: schema preprocessing & enrichment
CG->>PL: run transform plugins (page, block, router, utils, etc.)
note right of PL #E8F6E8: plugins add files/logs via context API (addFile, addLog)
CG->>PF: run transformEnd plugins (formatCode, finalize)
note right of PF #FFF4D6: formatting and final replacements
CG-->>Dev: return { errors, genResult, genLogs }
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 101
♻️ Duplicate comments (4)
packages/react-generator/test/generator/expected/appdemo01/src/http/axios.js (4)
129-135: Mirror disableMock explicit restore for linter parity.
116-128: Support single mock route objects here too.
136-137: AbortController note applies to expected outputs as well.
21-54: Mirror axios wrapper fixes in test expectations to keep tests green.Duplicate of the template change: apply the same polyfill hardening (no
thisalias, guarderror.response, remove unused expression, renameconfigtoreqConfig).Also applies to: 31-37
| { | ||
| "name": "@opentiny/tiny-engine-dsl-react", | ||
| "version": "0.1.0", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "main": "dist/tiny-engine-dsl-react.cjs.js", | ||
| "module": "dist/tiny-engine-dsl-react.es.js", | ||
| "files": [ | ||
| "dist" | ||
| ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Types are not published; add types and exports for proper TS/ESM/CJS resolution
The package ships only dist/ but doesn’t declare a "types" entry nor copy .d.ts into dist. Consumers will miss typings.
Apply this diff (ensure your build copies index.d.ts to dist):
{
"name": "@opentiny/tiny-engine-dsl-react",
"version": "0.1.0",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/tiny-engine-dsl-react.es.js",
+ "require": "./dist/tiny-engine-dsl-react.cjs.js",
+ "default": "./dist/tiny-engine-dsl-react.es.js"
+ }
+ },
"publishConfig": {
"access": "public"
},
"main": "dist/tiny-engine-dsl-react.cjs.js",
"module": "dist/tiny-engine-dsl-react.es.js",
"files": [
"dist"
],If you don’t already emit/copy d.ts, add a small step to the build pipeline to place the declarations under dist/.
I can add a minimal build step to copy d.ts into dist if you’d like.
📝 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.
| { | |
| "name": "@opentiny/tiny-engine-dsl-react", | |
| "version": "0.1.0", | |
| "publishConfig": { | |
| "access": "public" | |
| }, | |
| "main": "dist/tiny-engine-dsl-react.cjs.js", | |
| "module": "dist/tiny-engine-dsl-react.es.js", | |
| "files": [ | |
| "dist" | |
| ], | |
| { | |
| "name": "@opentiny/tiny-engine-dsl-react", | |
| "version": "0.1.0", | |
| "types": "dist/index.d.ts", | |
| "exports": { | |
| ".": { | |
| "types": "./dist/index.d.ts", | |
| "import": "./dist/tiny-engine-dsl-react.es.js", | |
| "require": "./dist/tiny-engine-dsl-react.cjs.js", | |
| "default": "./dist/tiny-engine-dsl-react.es.js" | |
| } | |
| }, | |
| "publishConfig": { | |
| "access": "public" | |
| }, | |
| "main": "dist/tiny-engine-dsl-react.cjs.js", | |
| "module": "dist/tiny-engine-dsl-react.es.js", | |
| "files": [ | |
| "dist" | |
| ], |
| "dependencies": { | ||
| "@opentiny/tiny-engine-builtin-component": "workspace:*", | ||
| "react-router-dom": "^6.27.0", | ||
| "vitest": "^1.0.0" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Move vitest to devDependencies (avoid shipping test runner to consumers)
vitest is a test runner and should not be a runtime dependency of the generator library.
Apply this diff:
"dependencies": {
"@opentiny/tiny-engine-builtin-component": "workspace:*",
- "react-router-dom": "^6.27.0",
- "vitest": "^1.0.0"
+ "react-router-dom": "^6.27.0"
},
"devDependencies": {
+ "vitest": "^1.0.0",📝 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.
| "dependencies": { | |
| "@opentiny/tiny-engine-builtin-component": "workspace:*", | |
| "react-router-dom": "^6.27.0", | |
| "vitest": "^1.0.0" | |
| }, | |
| "dependencies": { | |
| "@opentiny/tiny-engine-builtin-component": "workspace:*", | |
| "react-router-dom": "^6.27.0" | |
| }, | |
| "devDependencies": { | |
| "vitest": "^1.0.0" | |
| }, |
🤖 Prompt for AI Agents
In packages/react-generator/package.json around lines 29 to 33, the test runner
"vitest" is listed under "dependencies" but should be a dev-only tool; remove
the "vitest" entry from "dependencies" and add the same "vitest": "^1.0.0" entry
under "devDependencies" instead, then run your package manager install to update
node_modules and the lockfile so the change is persisted.
| "devDependencies": { | ||
| "@babel/eslint-parser": "^7.28.0", | ||
| "@babel/parser": "^7.28.0", | ||
| "@babel/traverse": "^7.28.0", | ||
| "@rushstack/eslint-patch": "^1.1.1", | ||
| "@types/node": "^18.0.0", | ||
| "dir-compare": "^4.0.0", | ||
| "eslint": "^8.12.0", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-plugin-react": "^7.37.5", | ||
| "fs-extra": "^10.0.1", | ||
| "prettier": "^2.6.1", | ||
| "vite": "^2.8.6", | ||
| "vite-plugin-static-copy": "^1.0.6", | ||
| "winston": "^3.10.0" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Ensure runtime dependencies are correctly classified
formatCode integration typically imports prettier at runtime. If so, prettier must be a dependency (not dev). Conversely, fs-extra/winston/dir-compare should be dependencies only if used at runtime by the library (not only in tests/build).
Run this script to confirm what’s imported under src/ and adjust dependency buckets:
- If prettier is used in src at runtime, move it to dependencies.
- If fs-extra/winston are used in src at runtime (not just tests/scripts), move them to dependencies; otherwise keep them in devDependencies.
- If react-router-dom is only referenced in templates for generated apps, consider declaring it in the template’s package.json rather than as a dependency of the generator itself.
🏁 Script executed:
#!/bin/bash
echo "Prettier runtime usage:"
rg -nP "from ['\"]prettier['\"]|require\\(['\"]prettier['\"]\\)" packages/react-generator/src || true
echo
echo "fs-extra & winston runtime usage:"
rg -nP "from ['\"](fs-extra|winston)['\"]|require\\(['\"](fs-extra|winston)['\"]\\)" packages/react-generator/src || true
echo
echo "react-router-dom generator usage (not generated template):"
rg -nP "from ['\"]react-router-dom['\"]" packages/react-generator/src || trueLength of output: 977
Adjust dependency classifications in packages/react-generator/package.json
Based on imports in your runtime code:
- Prettier is imported at runtime in src/utils/formatCode.js; it must live under dependencies, not devDependencies.
- React-router-dom is imported at runtime in src/plugins/genRouterPlugin.js; it also belongs in dependencies.
- Fs-extra and winston have no runtime imports under src—keep them in devDependencies.
- Dir-compare likewise remains a devDependency if only used for build/tests.
Required changes:
--- a/packages/react-generator/package.json
+++ b/packages/react-generator/package.json
@@
- "devDependencies": {
+ "dependencies": {
+ "prettier": "^2.6.1",
+ "react-router-dom": "^6.x.x"
+ },
+ "devDependencies": {
"@babel/eslint-parser": "^7.28.0",
"@babel/parser": "^7.28.0",
"@babel/traverse": "^7.28.0",
"@rushstack/eslint-patch": "^1.1.1",
"@types/node": "^18.0.0",
"dir-compare": "^4.0.0",
"eslint": "^8.12.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
- "fs-extra": "^10.0.1",
- "prettier": "^2.6.1",
+ "fs-extra": "^10.0.1",
+ /* prettier moved to dependencies */
"prettier": "^2.6.1",
"vite": "^2.8.6",
"vite-plugin-static-copy": "^1.0.6",
- "winston": "^3.10.0"
+ "winston": "^3.10.0"
}• Update the version of react-router-dom to match your project’s required version.
• If no "dependencies" section exists currently, insert it above "devDependencies" as shown.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/react-generator/package.json around lines 34 to 49, move
runtime-used packages into a top-level "dependencies" section (insert it above
"devDependencies" if one doesn't exist): remove "prettier" from devDependencies
and add it under "dependencies", add "react-router-dom" under "dependencies"
using the project-required version (update the version string accordingly), and
keep "fs-extra", "winston", and "dir-compare" in devDependencies as they are
only used at build/test time.
| /** | ||
| * 图标组件名,统一前缀为 TinyIcon,与从组件库引入的方法名 iconXxx 区分开,暂时不使用 | ||
| */ | ||
| const TINY_ICON = '' | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Icon prefix constant is empty — breaks icon detection and normalization
With const TINY_ICON = '', any startsWith(TINY_ICON) check returns true and the pre-processor never prefixes icon names. This causes incorrect icon recording and inconsistent naming.
Choose a concrete, non-empty prefix and align the comment. Based on the current pre-processor logic that produces Tiny${name}, set the prefix to Tiny:
-/**
- * 图标组件名,统一前缀为 TinyIcon,与从组件库引入的方法名 iconXxx 区分开,暂时不使用
- */
-const TINY_ICON = ''
+/**
+ * 图标组件统一前缀(与 iconXxx 方法名区分开)。与预处理保持一致:Tiny${name}
+ */
+const TINY_ICON = 'Tiny'Alternatively, if the desired final form is TinyIcon${name}, change both here and in pre-processor to use TinyIcon. I can provide that patch if preferred.
📝 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.
| /** | |
| * 图标组件名,统一前缀为 TinyIcon,与从组件库引入的方法名 iconXxx 区分开,暂时不使用 | |
| */ | |
| const TINY_ICON = '' | |
| /** | |
| * 图标组件统一前缀(与 iconXxx 方法名区分开)。与预处理保持一致:Tiny${name} | |
| */ | |
| const TINY_ICON = 'Tiny' |
🤖 Prompt for AI Agents
In packages/react-generator/src/constant/index.js around lines 395 to 399, the
TINY_ICON constant is an empty string which makes startsWith checks always true
and breaks icon detection; change the constant from '' to 'Tiny' (matching the
pre-processor that produces Tiny${name}), and update the accompanying comment to
state that icon component names are prefixed with 'Tiny' to distinguish them
from library imports; ensure any related normalization logic uses this constant
for prefix checks.
|
|
||
| // scriptConfig = { | ||
| // ...scriptConfig, | ||
| // ...newConfig | ||
| // } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented-out code in setScriptConfig and setStyleConfig
The configuration update logic is commented out, making these setters non-functional. Either implement the logic or document why it's disabled.
setScriptConfig: (newConfig) => {
if (!newConfig || typeof newConfig !== 'object') {
return
}
- // scriptConfig = {
- // ...scriptConfig,
- // ...newConfig
- // }
+ Object.assign(scriptConfig, newConfig)
}, setStyleConfig: (newConfig = {}) => {
if (!newConfig || typeof newConfig !== 'object') {
return
}
- // styleConfig = {
- // ...styleConfig,
- // ...newConfig
- // }
+ Object.assign(styleConfig, newConfig)
},Also applies to: 156-161
| setAge: () => | ||
| set((state) => { | ||
| function setAge(age) { | ||
| state.age = age | ||
| } | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zustand setters are no-ops (state never updates).
Both setters define inner functions that are never invoked; set receives a callback that does nothing. Replace with direct partial updates.
Apply this diff:
- setAge: () =>
- set((state) => {
- function setAge(age) {
- state.age = age
- }
- }),
+ setAge: (age) => set({ age }),📝 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.
| setAge: () => | |
| set((state) => { | |
| function setAge(age) { | |
| state.age = age | |
| } | |
| }), | |
| setAge: (age) => set({ age }), |
🤖 Prompt for AI Agents
In
packages/react-generator/test/generator/expected/appdemo01/src/stores/testState.js
around lines 8 to 13, the setter defines an inner function that is never called
so state never updates; change the setter to accept an age parameter and perform
a direct partial update via set (e.g. setAge: (age) => set(state => ({ ...state,
age })) or simply setAge: (age) => set({ age })) so the store state is actually
updated.
| setName: () => | ||
| set((state) => { | ||
| function setName(name) { | ||
| state.name = name | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue for setName.
Mirror the setAge fix.
- setName: () =>
- set((state) => {
- function setName(name) {
- state.name = name
- }
- })
+ setName: (name) => set({ name })📝 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.
| setName: () => | |
| set((state) => { | |
| function setName(name) { | |
| state.name = name | |
| } | |
| }) | |
| setName: (name) => set({ name }) |
🤖 Prompt for AI Agents
In
packages/react-generator/test/generator/expected/appdemo01/src/stores/testState.js
around lines 14 to 19, setName currently defines an inner function but never
updates state; mirror the setAge fix by making setName accept a name parameter
and use the outer set to update state.name (i.e., remove the unused inner
function and call set((state) => { state.name = name }) so the name is actually
applied).
| define: { | ||
| 'process.env': { ...process.env } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Security: leaking all environment variables to the client bundle
Defining 'process.env' as a spread of the host env inlines secrets into the browser bundle. Prefer import.meta.env with a whitelist (e.g., VITE_ prefix) and avoid exposing arbitrary envs.
- define: {
- 'process.env': { ...process.env }
- },
+ // Prefer using `import.meta.env` in app code.
+ // Keep `process.env` empty for legacy compatibility without leaking secrets.
+ define: {
+ 'process.env': {}
+ },If the template generator emits this, remember to update both the template and expected fixtures together.
📝 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.
| define: { | |
| 'process.env': { ...process.env } | |
| }, | |
| // Prefer using `import.meta.env` in app code. | |
| // Keep `process.env` empty for legacy compatibility without leaking secrets. | |
| define: { | |
| 'process.env': {} | |
| }, |
🤖 Prompt for AI Agents
packages/react-generator/test/generator/expected/appdemo01/vite.config.js lines
16-18: the config currently defines 'process.env' as a spread of the entire host
environment which leaks secrets to the client; replace this with a safe
whitelist approach that only injects permitted VITE_ vars (or an explicit list)
into the bundle by building an object from process.env filtered for /^VITE_/ (or
the explicit keys), JSON-stringifying the values, and assigning that object
instead of ...process.env; update the template emitter and the expected fixture
together so both the template and tests use the whitelist approach.
| console.log( | ||
| 'Statistics - equal entries: %s, distinct entries: %s, left only entries: %s, right only entries: %s, differences: %s', | ||
| result.equal, | ||
| result.distinct, | ||
| result.left, | ||
| result.right, | ||
| result.differences | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ESLint no-console violations (will fail CI) or move helper under test overrides.
The file triggers no-console errors in multiple places. Since this is a test-only helper, either:
- Disable the rule for this file, or
- Relocate it under the test utils folder to inherit test ESLint overrides.
Apply the minimal fix below to keep it where it is:
+/* eslint-disable no-console */
export const logDiffResult = (result) => {
console.log(
'Statistics - equal entries: %s, distinct entries: %s, left only entries: %s, right only entries: %s, differences: %s',
result.equal,
result.distinct,
result.left,
result.right,
result.differences
)Optionally, consider moving this file to packages/react-generator/test/utils/logDiffResult.js for parity with the Vue generator and to naturally allow console usage under test lint rules.
Also applies to: 19-21, 25-28, 30-31
🧰 Tools
🪛 ESLint
[error] 2-2: Unexpected console statement.
(no-console)
🤖 Prompt for AI Agents
packages/react-generator/utils/logDiffResult.js lines 2-9 (also occurrences at
19-21, 25-28, 30-31): this helper uses console.* causing ESLint no-console
failures; to minimally fix while keeping the file in place, add a file-level
ESLint directive at the top (e.g., /* eslint-disable no-console */) to allow
console calls for this test-only helper, or alternatively move the file to
packages/react-generator/test/utils/logDiffResult.js so it inherits test lint
overrides.
| export default defineConfig({ | ||
| build: { | ||
| lib: { | ||
| entry: path.resolve(__dirname, './src/index.js'), | ||
| formats: ['cjs', 'es'] | ||
| }, | ||
| sourcemap: true, | ||
| rollupOptions: { | ||
| external: ['@babel/parser', '@babel/traverse'] | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__dirname is undefined in ESM configs; this will throw at runtime.
Vite config in .mjs runs in ESM where __dirname is not defined. Compute it via fileURLToPath(import.meta.url) to avoid a ReferenceError.
import { defineConfig } from 'vite'
-import path from 'path'
+import path from 'path'
+import { fileURLToPath } from 'url'
import { viteStaticCopy } from 'vite-plugin-static-copy'
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, './src/index.js'),
formats: ['cjs', 'es']
},
sourcemap: true,
rollupOptions: {
external: ['@babel/parser', '@babel/traverse']
}
},Also applies to: 26-31
🤖 Prompt for AI Agents
In packages/react-generator/vite.config.mjs around lines 5-15 (and also lines
26-31), __dirname is used but undefined in ESM; replace its usage by deriving
the directory from import.meta.url: add an import from 'url' to get
fileURLToPath, compute const __filename = fileURLToPath(import.meta.url) and
const __dirname = path.dirname(__filename), then use that __dirname in the
build.lib.entry and any other occurrences; ensure the 'url' import is added at
the top of the file and remove direct reliance on the legacy __dirname global.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 87
♻️ Duplicate comments (40)
packages/react-generator/src/utils/uaperCase.js (1)
1-4: Fix filename typo and harden the util (type guard + param name)The file name “uaperCase.js” looks misspelled; also, using
stringas a parameter name is confusing and there’s no type guard. Please rename the file to something likecapitalizeFirstLetter.js(orcapitalize.js) and update imports accordingly. Add a light type check to avoid surprising errors if a non-string slips through.Inline change:
-export const capitalizeFirstLetter = (string) => { - if (!string) return string // 检查空字符串 - return string.charAt(0).toUpperCase() + string.slice(1) -} +export const capitalizeFirstLetter = (str) => { + if (!str) return str // empty/falsy short-circuit + if (typeof str !== 'string') return str + return str.charAt(0).toUpperCase() + str.slice(1) +}Follow-ups:
- Rename file (preserve history):
git mv packages/react-generator/src/utils/uaperCase.js packages/react-generator/src/utils/capitalizeFirstLetter.js- Update imports that reference the old path.
Verification sweep:
#!/bin/bash # Find lingering references to the old filename and symbol rg -nP -C1 "from '.*/uaperCase'|uaperCase" packages rg -nP -C1 '\bcapitalizeFirstLetter\b' packagesI can push a follow-up patch that performs the rename and import updates if you want.
packages/react-generator/src/templates/react-template/template-files/src/lowcodeConfig/store.js (1)
6-8: Bug: using factory’s $id before instantiation; also no guard for non-function exportsYou’re indexing with
$idon the factory instead of the created instance and not guarding non-function exports. This will yield undefined/mis-keyed entries or runtime errors. Fix by iterating entries, type‑checking, instantiating, and deriving the key from the instance (with sane fallbacks).Apply this diff:
- Object.values({ ...useDefinedStores }).forEach((store) => { - stores[store.$id] = store() - }) + Object.entries(useDefinedStores).forEach(([exportName, factory]) => { + if (typeof factory !== 'function') return + const instance = factory() + const id = instance?.$id ?? factory.$id ?? exportName + stores[id] = instance + })packages/react-generator/test/generator/expected/appdemo01/package.json (1)
3-3: Mark the generated app as private to avoid accidental publishTemplate/fixtures should not be publishable by default.
Apply this diff:
"version": "1.0.0", + "private": true,packages/react-generator/test/generator/expected/appdemo01/src/hooks/uselazy.jsx (1)
3-10: React.lazy misuse risk — support importer, promise, or component; add fallbackCurrent code breaks if a Component (not a promise) is passed, and lacks Suspense fallback. Make the API resilient to importer functions, dynamic import promises, or preloaded components.
Apply this diff:
-export const useLazy = (Component) => { - const Lazy = lazy(() => Component) - return (props) => ( - <Suspense> - <Lazy {...props} /> - </Suspense> - ) -} +export const useLazy = (importerOrComponent) => { + // Accept: + // 1) () => import('...') (preferred: true code-splitting) + // 2) import('...') promise (works with existing router usage) + // 3) Preloaded Component (wrap to module shape) + const factory = + typeof importerOrComponent === 'function' + ? importerOrComponent + : typeof importerOrComponent?.then === 'function' + ? () => importerOrComponent + : () => Promise.resolve({ default: importerOrComponent }) + const Lazy = lazy(factory) + return (props) => ( + <Suspense fallback={null}> + <Lazy {...props} /> + </Suspense> + ) +}packages/react-generator/utils/logDiffResult.js (1)
1-32: Fix ESLint no-console violations to prevent CI failure (test helper)This helper is console-driven; disable the rule at file scope (or move under test utils). Without this, ESLint will fail.
Apply this diff:
+/* eslint-disable no-console */ export const logDiffResult = (result) => { console.log( 'Statistics - equal entries: %s, distinct entries: %s, left only entries: %s, right only entries: %s, differences: %s', result.equal, result.distinct, result.left, result.right, result.differences )Optional: move to
packages/react-generator/test/utils/logDiffResult.jsto rely on test lint overrides.packages/react-generator/vite.config.mjs (1)
1-6: __dirname is undefined in ESM; compute from import.meta.url to avoid runtime errors.vite.config.mjs runs as ESM, so using __dirname will throw. Compute it via fileURLToPath.
Apply:
import { defineConfig } from 'vite' import path from 'path' +import { fileURLToPath } from 'url' import { viteStaticCopy } from 'vite-plugin-static-copy' +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + export default defineConfig({packages/react-generator/src/templates/react-template/template-files/packageJson.js (1)
19-28: Duplicate “antd” key; last one wins — remove the earlier duplicate.Static analysis is right: the first antd entry is overwritten by the later one. Keep a single entry.
dependencies: { '@opentiny/tiny-engine-i18n-host': '^1.0.0', // '@opentiny/react': 'latest', // '@opentiny/react-icon': 'latest', axios: 'latest', - antd: 'latest', 'axios-mock-adapter': '^1.19.0', react: '^18.3.1', 'react-dom': '^18.3.1', 'react-router-dom': '^6.27.0', '@vitejs/plugin-react': 'latest', "@ant-design/icons": "^6.0.0", "antd": "5.0.0", },packages/react-generator/package.json (3)
29-33: Move vitest to devDependencies (test-only).It doesn’t belong in runtime dependencies.
"dependencies": { "@opentiny/tiny-engine-builtin-component": "workspace:*", - "react-router-dom": "^6.27.0", - "vitest": "^1.0.0" + "react-router-dom": "^6.27.0" }, "devDependencies": { + "vitest": "^1.0.0",
34-49: Prettier is imported at runtime by the formatter—place it under dependencies.Your
formatCodeutility loads Prettier at runtime; keeping it in devDependencies will break consumers.- "devDependencies": { + "dependencies": { + "prettier": "^2.6.1", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { "@babel/eslint-parser": "^7.28.0", @@ - "prettier": "^2.6.1", "vite": "^2.8.6",#!/bin/bash # Confirm runtime Prettier usage in generator source rg -nP "from ['\"]prettier['\"]|require\\(['\"]prettier['\"]\\)" packages/react-generator/src || true
1-11: Add TypeScript declarations and an ESM/CJS-aware exports map
Consumers installing this package today won’t get typings by default, nor will modern bundlers or Node know which entrypoints to load for ESM vs. CJS.• Add a top-level
"types"entry pointing to your declaration file (e.g."dist/index.d.ts").
• Define an"exports"map so that:
–importloads the ES build,
–requireloads the CJS build,
–typespoints to your.d.tsfile, and
–defaultprovides a fallback.
• Verify your build pipeline actually emits/copiesindex.d.tsintodist/.{ "name": "@opentiny/tiny-engine-dsl-react", "version": "0.1.0", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/tiny-engine-dsl-react.es.js", + "require": "./dist/tiny-engine-dsl-react.cjs.js", + "default": "./dist/tiny-engine-dsl-react.es.js" + } + }, "publishConfig": { "access": "public" }, "main": "dist/tiny-engine-dsl-react.cjs.js", "module": "dist/tiny-engine-dsl-react.es.js", "files": [ "dist" ],Ensure you update your build (e.g.
tsc --declaration) or add a post-build step to emit/copyindex.d.tsintodist/.packages/react-generator/src/constant/index.js (1)
395-399: Icon prefix constant is empty — breaks icon detection/normalizationWith TINY_ICON set to '', any startsWith(TINY_ICON) check is trivially true. This prevents correct detection and normalization of icon component names and will skew import/usage logic.
Apply this diff to set a concrete prefix and align the comment with the actual convention:
-/** - * 图标组件名,统一前缀为 TinyIcon,与从组件库引入的方法名 iconXxx 区分开,暂时不使用 - */ -const TINY_ICON = '' +/** + * 图标组件统一前缀(与 iconXxx 方法名区分)。和预处理保持一致:Tiny${name} + */ +const TINY_ICON = 'Tiny'If the desired final form is TinyIcon${name}, update both here and the pre-processor to use 'TinyIcon'.
packages/react-generator/test/generator/expected/appdemo01/src/App.jsx (1)
1-11: Router file downstream uses invalidredirectprop and stray commas (React Router v6)This App looks fine, but the referenced router has issues (invalid
redirectprop; extra commas). Track/fix in the router file; otherwise App boot will throw. Comment previously raised; echoing here for visibility.Use this to spot invalid props/comma artifacts:
#!/bin/bash rg -nC2 --glob "packages/**/expected/**/src/router/index.jsx" -P '\bredirect\s*=|,\s*<Route'packages/react-generator/test/generator/expected/appdemo01/vite.config.js (1)
16-18: Security/privacy: leaking all host environment variables into the client bundleDefining
define: { 'process.env': { ...process.env } }inlines every env var (including secrets) into the browser bundle. Vite already exposes whitelisted vars asimport.meta.env(prefixed by VITE_ by default). Remove this or strictly whitelist.Minimal safe change:
- define: { - 'process.env': { ...process.env } - }, + // Use import.meta.env (Vite) for client-readable env; avoid leaking host env. + define: { + 'process.env': {} + },If you must support legacy
process.env.Xin app code, inject only selected keys:- define: { - 'process.env': { ...process.env } - }, + define: { + 'process.env': Object.fromEntries( + Object.entries(process.env) + .filter(([k]) => /^VITE_/.test(k)) // whitelist + .map(([k, v]) => [k, JSON.stringify(v ?? '')]) + ) + },Remember to update both the template emitter and the expected fixture together.
packages/react-generator/src/templates/react-template/template-files/src/hooks/useLazy.jsx (2)
1-10: Unify filename/import casing: useLazy.jsx vs uselazy.jsxThere are imports elsewhere referencing
../hooks/uselazy. On case-sensitive filesystems this will fail with the current filenameuseLazy.jsx. Standardize onuseLazyacross generator and fixtures.Run to verify and patch callsites:
#!/bin/bash # Find hook files fd -a -t f '(?i)uselazy\.jsx$' packages || true # Find imports referencing the hook (case-insensitive) rg -n -i -C1 "from ['\"][^'\"]*/hooks/(useLazy|uselazy)['\"]|import\s+\{\s*useLazy\s*\}" packages/react-generator # Optional: show generated fixtures referencing uselazy rg -n -i "hooks/uselazy" packages/react-generator/test || true
3-10: Fix React.lazy usage and add Suspense fallback to avoid runtime errors
lazy(() => Component)is invalid unlessComponentis a Promise resolving to a module with a default export. This will break when a plain component function/class or an import loader function is passed. Also, Suspense should have a fallback. Make the hook accept either a loader function() => import('...')or a promise, normalize it, and provide a configurable fallback.-import { lazy, Suspense } from 'react' - -export const useLazy = (Component) => { - const Lazy = lazy(() => Component) - return (props) => ( - <Suspense> - <Lazy {...props} /> - </Suspense> - ) -} +import { lazy, Suspense } from 'react' + +// Accepts either: +// - a function returning a dynamic import () => import('...'), or +// - a dynamic import Promise (import('...')), or +// - a component value (will be wrapped as default export) +export const useLazy = (loaderOrComponent, { fallback = null } = {}) => { + const factory = + typeof loaderOrComponent === 'function' + ? loaderOrComponent + : () => loaderOrComponent + + const Lazy = lazy(() => + Promise.resolve(factory()).then((mod) => + mod && typeof mod === 'object' && 'default' in mod ? mod : { default: mod } + ) + ) + + return (props) => ( + <Suspense fallback={fallback}> + <Lazy {...props} /> + </Suspense> + ) +}packages/react-generator/src/templates/react-template/template-files/src/http/index.js (1)
19-24: Don’t swallow Axios errors; guard optional fields and rethrow
error.responsecan be undefined (network/CORS). Accessingresponse.statuswould throw, and not returning a rejection causes the promise chain to resolve withundefined.- http.interceptors.response.use(dataHandler, (error) => { - const response = error.response - if (response.status === 403 && response.headers && response.headers['x-login-url']) { - // TODO 处理无权限时,重新登录再发送请求 - } - }) + http.interceptors.response.use( + dataHandler, + (error) => { + const response = error?.response + if (response?.status === 403 && response.headers?.['x-login-url']) { + // TODO: 处理无权限时,触发登录并可选重试原请求 + // 例如: + // const loginUrl = response.headers['x-login-url'] + // await redirectToLogin(loginUrl) + // return http.request(error.config) + } + return Promise.reject(error) + } + )packages/react-generator/test/generator/expected/appdemo01/src/http/index.js (1)
19-24: Mirror fix in generated fixture: guard and rethrow errorsSame interceptor issue as the template: optional-chain
error.responseand returnPromise.reject(error)to propagate failures.- http.interceptors.response.use(dataHandler, (error) => { - const response = error.response - if (response.status === 403 && response.headers && response.headers['x-login-url']) { - // TODO 处理无权限时,重新登录再发送请求 - } - }) + http.interceptors.response.use( + dataHandler, + (error) => { + const response = error?.response + if (response?.status === 403 && response.headers?.['x-login-url']) { + // TODO 处理无权限时,重新登录再发送请求 + } + return Promise.reject(error) + } + )packages/react-generator/test/generator/expected/appdemo01/src/stores/testState.js (2)
8-13: Zustand setter is a no-op — state never updates (inner function not invoked).The callback passed to set defines an inner function but never calls it; with plain Zustand (no immer middleware), mutations inside set’s callback won’t apply unless you return a new partial. Update to a parameterized setter that performs a partial update.
Apply this diff:
- setAge: () => - set((state) => { - function setAge(age) { - state.age = age - } - }), + setAge: (age) => set({ age }),
14-19: Same bug for setName — replace with a proper partial update.Mirror the setAge fix so updates actually apply.
- setName: () => - set((state) => { - function setName(name) { - state.name = name - } - }) + setName: (name) => set({ name })packages/react-generator/src/plugins/genTemplatePlugin.js (1)
8-19: Standardize plugin behavior: always mutatecontext.genResultand also return files.Current branches either return files or push into
context.genResult, leading to inconsistent behavior and potential drops. Unify: resolve the chosen template (options.template > context.template > 'default'), normalize results to an array, push tocontext.genResult, and return the same array. Also passcontextwhen calling template functions for symmetry.- run(schema, context) { - if (typeof options?.template === 'function') { - const res = options.template(schema, context) - if (Array.isArray(res)) { - return res - } - - if (res?.fileContent && res?.fileName) { - return res - } - - return - } - - const template = context?.template || 'default' - - if (!template) { - return - } - - if (typeof template === 'function') { - context.genResult.push(...(template(schema) || [])) - return - } - - if (templateMap[template]) { - context.genResult.push(...templateMap[template](schema)) - } - } + run(schema, context) { + const chosen = options?.template ?? context?.template ?? 'default' + if (!chosen) return + + const callTemplate = (tpl) => tpl?.(schema, context) + const res = + typeof chosen === 'function' + ? callTemplate(chosen) + : (templateMap[chosen] && callTemplate(templateMap[chosen])) || [] + + const files = Array.isArray(res) ? res : res?.fileName ? [res] : [] + if (files.length) { + context.genResult.push(...files) + } + return files + }Also applies to: 27-35
packages/react-generator/src/plugins/genPagePlugin.js (2)
21-24: Guard against missing/malformed schema inputsRun currently assumes schema.pageSchema exists and is iterable, which can throw.
Apply this diff:
- run(schema) { - const pages = schema.pageSchema - const componentsMap = schema.componentsMap || [] + run(schema = {}) { + const { pageSchema = [], componentsMap = [] } = schema || {} + const pages = Array.isArray(pageSchema) ? pageSchema : []
27-39: Only mark a single entry pageisEntry: true for all pages is incorrect. Respect page flag if present; otherwise default to the first page.
Apply this diff:
- pages.forEach((pageSchema) => { + pages.forEach((pageSchema, index) => { const fileName = pageSchema.fileName const pageInfo = { schema: pageSchema, name: fileName } // 使用 generatePageCode 生成页面代码 - const pagePanels = generatePageCode({ + const pagePanels = generatePageCode({ pageInfo, componentsMap, - isEntry: true + isEntry: Boolean(pageSchema.isEntry) || index === 0 })packages/react-generator/src/pre-processor/index.js (1)
33-38: Icon prefix logic breaks when TINY_ICON is empty; add fallbackTINY_ICON is currently an empty string; startsWith('') is always true, so no prefix will be added. Use a non-empty fallback (e.g., 'TinyIcon').
Apply this diff:
- if (componentName === BUILTIN_COMPONENT_NAME.ICON && props.name) { - // 图标组件名,统一前缀为 TinyIcon,与从组件库引入的方法名 iconXxx 区分开 - const iconName = props.name.startsWith(TINY_ICON) ? props.name : `Tiny${props.name}` + if (componentName === BUILTIN_COMPONENT_NAME.ICON && props.name) { + // 图标组件名,统一前缀为 TinyIcon,与从组件库引入的方法名 iconXxx 区分开 + const prefix = TINY_ICON || 'TinyIcon' + const iconName = props.name.startsWith(prefix) ? props.name : `${prefix}${props.name}` schema.componentName = iconName delete props.name }packages/react-generator/src/plugins/parseSchemaPlugin.js (3)
13-18: Guard against missing/invalid pageSchema and avoid unconditional mutation of componentsMapThis will throw if
pageSchemais missing. Also, blindly spreadingschema.componentsMapassumes it’s an array and can duplicate built-ins.- run(schema) { - const { pageSchema } = schema + run(schema) { + const { pageSchema } = schema || {} + if (!Array.isArray(pageSchema) || pageSchema.length === 0) { + return + } const pagesMap = {} const resPageTree = [] - schema.componentsMap = [...schema.componentsMap, ...BUILTIN_COMPONENTS_MAP] + const mapByName = new Map((schema.componentsMap || []).map((c) => [c.componentName, c])) + for (const c of BUILTIN_COMPONENTS_MAP) mapByName.set(c.componentName, c) + schema.componentsMap = Array.from(mapByName.values())
28-31: Deep-clone meta to avoid mutating source itemsSpreading
componentItemkeeps the samemetareference; later mutations tonewComponentItem.metawill leak back.- const newComponentItem = { - ...componentItem - } + const newComponentItem = { + ...componentItem, + meta: { ...(componentItem.meta || {}) } + }
35-42: Parent-chain walk lacks existence checks; can throw on broken referencesIf a
parentIdis missing frompagesMap,preFolderis undefined, andpreFolder.metawill throw.- while (curParentId !== '0' && depth < 1000) { - const preFolder = pagesMap[curParentId] - - path = `${preFolder.meta.name}${path ? '/' : ''}${path}` - newComponentItem.meta.router = `${preFolder.meta.router}/${newComponentItem.meta.router}` - curParentId = preFolder.meta.parentId - depth++ - } + while (curParentId !== '0' && depth < 1000) { + const preFolder = pagesMap[curParentId] + if (!preFolder || !preFolder.meta) break + + const seg = preFolder.meta.name || '' + path = `${seg}${path ? '/' : ''}${path}` + const parentRouter = preFolder.meta.router || '' + const childRouter = newComponentItem.meta.router || '' + newComponentItem.meta.router = `${parentRouter}/${childRouter}`.replace(/\/+/g, '/') + curParentId = preFolder.meta.parentId + depth++ + }packages/react-generator/src/generator/page.js (4)
26-26: Fix typo in import path: uaperCase → upperCase-import { capitalizeFirstLetter } from '../utils/uaperCase' +import { capitalizeFirstLetter } from '../utils/upperCase'Note: ensure the file is renamed to utils/upperCase.js to match the import.
Run to find all references to the misspelled path and update them:
#!/bin/bash rg -n "../utils/uaperCase" -C1
96-96: Resolve "used before defined" mutual recursion between recurseJSXChildren and generateJSXNodeMinimal change: forward-declare the variable and convert the later const to an assignment.
// 工具函数:处理JSX子元素 -const recurseJSXChildren = (children, state, description, result, isClassComponent = false) => { +let generateJSXNode +const recurseJSXChildren = (children, state, description, result, isClassComponent = false) => {-const generateJSXNode = (schema, state, description, isRootNode = false, isClassComponent = false) => { +generateJSXNode = (schema, state, description, isRootNode = false, isClassComponent = false) => {Also applies to: 274-274
192-192: Remove unused parameters from handleJSXDataBinding and its call site-const handleJSXDataBinding = ({ key, item, attrsArr, description, state, isClassComponent = false, hasOnChange = false, componentName = '' }) => { +const handleJSXDataBinding = ({ key, item, attrsArr, isClassComponent = false, hasOnChange = false, componentName = '' }) => {- const result = handleJSXDataBinding({key, item, attrsArr, description, state, isClassComponent, hasOnChange, componentName}) + const result = handleJSXDataBinding({ key, item, attrsArr, isClassComponent, hasOnChange, componentName })Also applies to: 245-246
437-440: Replace require('path') with ES module import- const path = require('path') - const relativePath = path?.relative(fromPath, toPath).replace(/\\/g, '/') + const relativePath = path.relative(fromPath, toPath).replace(/\\/g, '/')And add the import near the top:
import { getTypeOfSchema, avoidDuplicateString, toPascalCase, prettierOpts, isOn, addAccessorRecord, addIconRecord, handleIconInProps, getFunctionInfo, formatCode } from '../utils' +import path from 'path'Also applies to: 13-24
packages/react-generator/src/generator/generateJsx.js (1)
141-151: Uncomment and implement config setters to make hooks effectiveThese setters are currently no-ops; update to merge the provided config objects.
setScriptConfig: (newConfig) => { if (!newConfig || typeof newConfig !== 'object') { return } - - // scriptConfig = { - // ...scriptConfig, - // ...newConfig - // } + Object.assign(scriptConfig, newConfig) },setStyleConfig: (newConfig = {}) => { if (!newConfig || typeof newConfig !== 'object') { return } - - // styleConfig = { - // ...styleConfig, - // ...newConfig - // } + Object.assign(styleConfig, newConfig) },Also applies to: 152-162
packages/react-generator/src/plugins/genGlobalState.js (2)
62-64: Replace all occurrences of this. → state. and support function bodies.
.replace('this.', 'state.')only replaces the first occurrence and misses nested access. Use a global replacement; consider parsing function signatures if you need to retain parameters.Apply minimal safe fix:
- ([key, value]) => `${key}: () => set((state) => { - ${value.value.replace('this.', 'state.')} - })` + ([key, value]) => `${key}: () => set((state) => { + ${value.value.replace(/this\./g, 'state.')} + })`If you need parameter support, I can provide a robust transform using
getFunctionInfosimilar to your parser utilities.
51-55: Fix value-serialization logic (falsy primitives, objects, and strings).The current condition
if (value || typeof value === 'object')is inconsistent (e.g., 0 and '' bypass JSON.stringify, while truthy primitives are stringified). Serialize by type instead, preserving numbers/booleans, quoting strings, and JSON-stringifying objects/null.Apply:
- if (value || typeof value === 'object') { - value = JSON.stringify(value) - } + if (value === null) { + value = 'null' + } else if (typeof value === 'object') { + value = JSON.stringify(value) + } else if (typeof value === 'string') { + // Preserve quotes correctly; JSON.stringify handles embedded quotes/escapes. + value = JSON.stringify(value) + } else if (typeof value === 'number' || typeof value === 'boolean') { + // keep as-is + } else if (typeof value === 'undefined') { + value = 'undefined' + }packages/react-generator/src/parser/jsx-slot.js (5)
113-113: ESLint: replace short-circuit push with explicit ifSatisfy no-unused-expressions and improve readability.
- condition && result.push(' }') + if (condition) { + result.push(' }') + }
24-29: Event args forwarding bug: passes a single array instead of variadic argsSpread the collected args when invoking the handler.
- const extendParams = item.params.join(',') - - return attrsArr.push(`${key}={(...eventArgs) => ${propValue}(eventArgs, ${extendParams})}`) + const extendParams = item.params.join(',') + return attrsArr.push(`${key}={(...eventArgs) => ${propValue}(...eventArgs, ${extendParams})}`)
34-60: Improve binding handler: support JSDataBinding; avoid map-for-side-effects; fix literals
- Include
JSDataBindingas an expression type.- Use
forEachinstead ofmapfor side effects.- Use JSON.stringify for strings; omit false booleans.
-function handleBinding(props, attrsArr, description) { - Object.entries(props).map(([key, item]) => { +function handleBinding(props, attrsArr, description) { + Object.entries(props).forEach(([key, item]) => { const propType = item?.type || 'literal' - if (['JSExpression', 'JSFunction'].includes(propType)) { + if (['JSExpression', 'JSFunction', 'JSDataBinding'].includes(propType)) { return handleExprBinding(key, item, attrsArr) } @@ - if (typeof item === 'string') return attrsArr.push(`${key}="${item}"`) - if (typeof item === 'boolean') return attrsArr.push(item ? key : '') + if (typeof item === 'string') return attrsArr.push(`${key}=${JSON.stringify(item)}`) + if (typeof item === 'boolean') { + if (item) attrsArr.push(key) + return + } } @@ - return attrsArr.push(`${key}={${JSON.stringify(item)}}`) - }) + return attrsArr.push(`${key}={${JSON.stringify(item)}}`) + }) }
74-76: Icon detection runs for every component because TINY_ICON is ''Guard against empty prefix; otherwise
startsWith('')is always true.- if (componentName?.startsWith(TINY_ICON)) { + if (TINY_ICON && componentName?.startsWith(TINY_ICON)) { addIconRecord(description, componentName) }
100-109: Children array: handle strings and simple expressions without recursion crashWhen children include strings or JSExpression/JSDataBinding nodes, directly render them instead of destructuring inside recursive calls.
- if (Array.isArray(children)) { - const subTemplate = children.map((child) => generateJSXTemplate(child, description)).join('') - result.push(subTemplate) - } else if (children?.type === 'JSExpression') { + if (Array.isArray(children)) { + const subTemplate = children + .map((child) => { + if (typeof child === 'string') return child + if (child?.type === 'JSExpression' || child?.type === 'JSDataBinding') { + return `{ ${child.value.replace(/this\./g, '')} }` + } + if (child?.type === 'i18n') { + return `{t('${child.key}')}` + } + return generateJSXTemplate(child, description) + }) + .join('') + result.push(subTemplate) + } else if (children?.type === 'JSExpression') { result.push(`{ ${children.value.replace(/this\./g, '')} }`) } else if (children?.type === 'i18n') {packages/react-generator/test/generator/expected/appdemo01/src/http/axios.js (2)
129-133: no-unused-expressions:mock && mock.restore()Mirror the fix suggested for the template.
- disableMock() { - mock && mock.restore() - mock = undefined - }, + disableMock() { + if (mock) { + mock.restore() + mock = undefined + } + },
31-37: no-unused-expressions: replace short-circuit with explicit assignmentSame as template: avoid
&&for side-effects; guarderror.response.- /* eslint-disable no-useless-call */ - typeof handleData === 'function' && (data = handleData.call(null, data, setting)) + if (typeof handleData === 'function') { + /* eslint-disable no-useless-call */ + data = handleData.call(null, data, setting) + } ... - .catch((error) => { - resolve([error.response.status, error.response.data]) - }) + .catch((error) => { + const status = error?.response?.status ?? 500 + const payload = error?.response?.data ?? { message: error?.message } + resolve([status, payload]) + })
| const DEFAULT_COMPONENTS_MAP = [ | ||
| { | ||
| componentName: 'TinyAlert', | ||
| exportName: 'Alert', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyBreadcrumb', | ||
| exportName: 'Breadcrumb', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyBreadcrumbItem', | ||
| exportName: 'Breadcrumb.Item', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyButton', | ||
| exportName: 'Button', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyButtonGroup', | ||
| exportName: 'Button.Group', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyCheckbox', | ||
| exportName: 'Checkbox', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyCheckboxButton', | ||
| exportName: 'Checkbox.Button', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyCheckboxGroup', | ||
| exportName: 'Checkbox.Group', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyCol', | ||
| exportName: 'Col', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyCollapse', | ||
| exportName: 'Collapse', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyCollapseItem', | ||
| exportName: 'Collapse.Panel', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyDatePicker', | ||
| exportName: 'DatePicker', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyDialogBox', | ||
| exportName: 'Modal', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyFloatbar', | ||
| exportName: 'FloatButton', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyForm', | ||
| exportName: 'Form', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyFormItem', | ||
| exportName: 'Form', | ||
| subName: 'Item', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyGrid', | ||
| exportName: 'Row', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyInput', | ||
| exportName: 'Input', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyInputNumber', | ||
| exportName: 'InputNumber', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyTextarea', | ||
| exportName: 'TextArea', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyModal', | ||
| exportName: 'Modal', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyNumeric', | ||
| exportName: 'Numeric', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyPager', | ||
| exportName: 'Pager', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyPopeditor', | ||
| exportName: 'Popeditor', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyPopover', | ||
| exportName: 'Popover', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyRadio', | ||
| exportName: 'Radio', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyRow', | ||
| exportName: 'Row', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinySearch', | ||
| exportName: 'Search', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinySelect', | ||
| exportName: 'Select', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinySwitch', | ||
| exportName: 'Switch', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyTabs', | ||
| exportName: 'Tabs', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyTabItem', | ||
| exportName: 'TabItem', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyTimeLine', | ||
| exportName: 'TimeLine', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyTooltip', | ||
| exportName: 'Tooltip', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, | ||
| { | ||
| componentName: 'TinyTree', | ||
| exportName: 'Tree', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| } | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Version inconsistency: DEFAULT_COMPONENTS_MAP uses AntD ^3.10.0 while AntdComponents uses ^5.16.0
Mixing major versions risks mismatched API usage and lockfile conflicts when the dependency graph is generated. Align all AntD versions to the same range.
Minimal, maintainable change (define a single version constant and reuse it):
+// Centralize the Ant Design version to avoid drift
+const ANT_DESIGN_VERSION = '^5.16.0'
...
- version: '^3.10.0',
+ version: ANT_DESIGN_VERSION,Apply the replacement to every AntD entry in DEFAULT_COMPONENTS_MAP and BUILTIN mappings that depend on AntD.
Verification script to spot lingering mismatches:
Also applies to: 291-380
🏁 Script executed:
#!/bin/bash
# Find any AntD mappings using a version other than ^5
rg -nP "package:\s*'antd'.*?\n\s*version:\s*'[^']+'" -C1 packages/react-generator/src/constant/index.jsLength of output: 120
🏁 Script executed:
#!/bin/bash
# Show all AntD component mappings with their versions
rg -n "package: 'antd'," -A1 packages/react-generator/src/constant/index.js
echo "--- Entries using ^5.x ---"
rg -n "version: '\^5" -C1 packages/react-generator/src/constant/index.js
echo "--- Entries using ^3.x ---"
rg -n "version: '\^3" -C1 packages/react-generator/src/constant/index.jsLength of output: 6624
Unify AntD Version in DEFAULT_COMPONENTS_MAP and BUILTIN Mappings
I confirmed that the early mappings in packages/react-generator/src/constant/index.js (lines 21–261) all use version ^3.10.0, while the later BUILTIN AntD entries (lines 296–378) use ^5.16.0. This major-version drift can lead to API mismatches and lockfile conflicts when generating the dependency graph.
• File: packages/react-generator/src/constant/index.js
– DEFAULT_COMPONENTS_MAP entries (lines 21–261) – version: '^3.10.0'
– BUILTIN AntD mappings (lines 296–378) – version: '^5.16.0'
Minimal, maintainable refactor: define a single version constant and reuse it everywhere.
+// Centralize the Ant Design version to avoid drift
+const ANT_DESIGN_VERSION = '^5.16.0'
const DEFAULT_COMPONENTS_MAP = [
{
componentName: 'TinyAlert',
exportName: 'Alert',
package: 'antd',
- version: '^3.10.0',
+ version: ANT_DESIGN_VERSION,
destructuring: true
},
// …
]
const BUILTIN = [
// …
{
componentName: 'AntdBreadcrumb',
exportName: 'Breadcrumb',
package: 'antd',
- version: '^5.16.0',
+ version: ANT_DESIGN_VERSION,
destructuring: true
},
// …
]Verification script (rerun after refactor to ensure no hard-coded versions remain):
#!/bin/bash
rg -nP "package:\s*'antd'.*?\n\s*version:\s*'[^']+'" -C1 packages/react-generator/src/constant/index.js🤖 Prompt for AI Agents
In packages/react-generator/src/constant/index.js around lines 21–261 and
296–378, multiple entries hard-code different AntD versions (^3.10.0 and
^5.16.0); create a single constant (e.g., ANT_DESIGN_VERSION = '^5.16.0' or the
agreed version) at the top of this file and replace every hard-coded version
value for package: 'antd' with that constant; ensure any BUILTIN mappings and
DEFAULT_COMPONENTS_MAP use the constant, adjust any exports/imports if needed,
then run the provided ripgrep verification script to confirm no remaining
hard-coded AntD versions.
| componentName: 'TinyBreadcrumbItem', | ||
| exportName: 'Breadcrumb.Item', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix AntD subcomponent mapping for Breadcrumb.Item
Using exportName: 'Breadcrumb.Item' without subName is inconsistent with the rest of the map and will likely produce an invalid named import. Prefer exportName + subName.
{
componentName: 'TinyBreadcrumbItem',
- exportName: 'Breadcrumb.Item',
+ exportName: 'Breadcrumb',
+ subName: 'Item',
package: 'antd',
version: '^3.10.0',
destructuring: true
},📝 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.
| componentName: 'TinyBreadcrumbItem', | |
| exportName: 'Breadcrumb.Item', | |
| package: 'antd', | |
| version: '^3.10.0', | |
| destructuring: true | |
| }, | |
| { | |
| componentName: 'TinyBreadcrumbItem', | |
| exportName: 'Breadcrumb', | |
| subName: 'Item', | |
| package: 'antd', | |
| version: '^3.10.0', | |
| destructuring: true | |
| }, |
🤖 Prompt for AI Agents
In packages/react-generator/src/constant/index.js around lines 33 to 38, the
AntD mapping uses exportName: 'Breadcrumb.Item' which is inconsistent and will
produce an invalid named import; change this entry to use exportName:
'Breadcrumb' and add subName: 'Item' (keep package, version and destructuring
as-is) so the mapping becomes exportName: 'Breadcrumb', subName: 'Item'.
| componentName: 'TinyButtonGroup', | ||
| exportName: 'Button.Group', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix AntD subcomponent mapping for Button.Group
Same issue as above: Use exportName 'Button' with subName 'Group'.
{
componentName: 'TinyButtonGroup',
- exportName: 'Button.Group',
+ exportName: 'Button',
+ subName: 'Group',
package: 'antd',
version: '^3.10.0',
destructuring: true
},📝 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.
| componentName: 'TinyButtonGroup', | |
| exportName: 'Button.Group', | |
| package: 'antd', | |
| version: '^3.10.0', | |
| destructuring: true | |
| }, | |
| { | |
| componentName: 'TinyButtonGroup', | |
| exportName: 'Button', | |
| subName: 'Group', | |
| package: 'antd', | |
| version: '^3.10.0', | |
| destructuring: true | |
| }, |
🤖 Prompt for AI Agents
In packages/react-generator/src/constant/index.js around lines 47 to 52, the
AntD subcomponent mapping is incorrect: replace exportName 'Button.Group' with
exportName 'Button' and add a new property subName: 'Group' (keep destructuring
true and other fields unchanged) so the generator treats Group as a subcomponent
of Button rather than a compound export.
| componentName: 'TinyTextarea', | ||
| exportName: 'TextArea', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TextArea mapping is incorrect for AntD
AntD exposes TextArea as Input.TextArea, not a top-level TextArea named export. Adjust to use subName.
{
componentName: 'TinyTextarea',
- exportName: 'TextArea',
+ exportName: 'Input',
+ subName: 'TextArea',
package: 'antd',
version: '^3.10.0',
destructuring: true
},📝 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.
| componentName: 'TinyTextarea', | |
| exportName: 'TextArea', | |
| package: 'antd', | |
| version: '^3.10.0', | |
| destructuring: true | |
| }, | |
| { | |
| componentName: 'TinyTextarea', | |
| exportName: 'Input', | |
| subName: 'TextArea', | |
| package: 'antd', | |
| version: '^3.10.0', | |
| destructuring: true | |
| }, |
🤖 Prompt for AI Agents
In packages/react-generator/src/constant/index.js around lines 153 to 158, the
mapping incorrectly treats AntD's TextArea as a top-level export; update the
mapping to use the Input export with a subName. Change exportName from
'TextArea' to 'Input' and add subName: 'TextArea' (keep package, version and
destructuring as-is) so the generator looks up Input.TextArea instead of a
non-existent top-level TextArea.
| componentName: 'TinyNumeric', | ||
| exportName: 'Numeric', | ||
| package: 'antd', | ||
| version: '^3.10.0', | ||
| destructuring: true | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Several mappings point to non-existent AntD exports
- TinyNumeric → AntD does not export Numeric; likely InputNumber.
- TinyPager → AntD uses Pagination.
- TinyPopeditor → Not an AntD component; mapping will break imports.
Proposed fixes:
{
componentName: 'TinyNumeric',
- exportName: 'Numeric',
+ exportName: 'InputNumber',
package: 'antd',
version: '^3.10.0',
destructuring: true
},
{
componentName: 'TinyPager',
- exportName: 'Pager',
+ exportName: 'Pagination',
package: 'antd',
version: '^3.10.0',
destructuring: true
},
- {
- componentName: 'TinyPopeditor',
- exportName: 'Popeditor',
- package: 'antd',
- version: '^3.10.0',
- destructuring: true
- },
+ // Removed TinyPopeditor: no AntD equivalent. Consider mapping to Popover/Popconfirm or remove entirely.Please confirm the intended UX for TinyPopeditor so we can map it appropriately (e.g., Popover + Form, or a custom component).
Also applies to: 174-179, 181-186
🤖 Prompt for AI Agents
In packages/react-generator/src/constant/index.js around lines 167-172 (and
similarly for 174-179 and 181-186), several component mappings point to
non-existent AntD exports: replace componentName 'TinyNumeric' mapping to AntD's
'InputNumber' (exportName: 'InputNumber'); replace 'TinyPager' mapping to AntD's
'Pagination' (exportName: 'Pagination'); and remove or remap 'TinyPopeditor'
since AntD has no direct equivalent — decide whether it should map to a
combination (e.g., 'Popover' + form-controlled inputs) or a custom component and
update the mapping accordingly (set package to 'antd' and exportName to
'Popover' if using Popover, or point to your internal component path), then run
a quick import check to ensure these exportName values exist in the target
package.
| // 写入文件 | ||
| for (const { fileName, path: filePath, fileContent } of genResult) { | ||
| fs.mkdirSync(path.resolve(__dirname, `./result/appdemo01/${filePath}`), { recursive: true }) | ||
|
|
||
| if (typeof fileContent === 'string') { | ||
| fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), fileContent) | ||
| } else if (fileContent instanceof Blob) { | ||
| const arrayBuffer = await fileContent.arrayBuffer() | ||
| const buffer = Buffer.from(arrayBuffer) | ||
|
|
||
| fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), buffer) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Harden file emission: clean output dir, normalize write targets, and handle binary-ish contents.
- Pre-clean the result directory to avoid stale artifacts affecting diffs.
- Centralize output root to reduce path repetition/typos.
- Support Buffer/Uint8Array in addition to string/Blob.
- Guard
Blobusage for Node versions whereBlobmay not exist.
- // 写入文件
- for (const { fileName, path: filePath, fileContent } of genResult) {
- fs.mkdirSync(path.resolve(__dirname, `./result/appdemo01/${filePath}`), { recursive: true })
-
- if (typeof fileContent === 'string') {
- fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), fileContent)
- } else if (fileContent instanceof Blob) {
- const arrayBuffer = await fileContent.arrayBuffer()
- const buffer = Buffer.from(arrayBuffer)
-
- fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), buffer)
- }
- }
+ // 写入文件
+ const outRoot = path.resolve(__dirname, './result/appdemo01')
+ fs.rmSync(outRoot, { recursive: true, force: true })
+ for (const { fileName, path: filePath, fileContent } of genResult) {
+ const outDir = path.join(outRoot, filePath || '')
+ fs.mkdirSync(outDir, { recursive: true })
+ const dest = path.join(outDir, fileName)
+
+ if (typeof fileContent === 'string') {
+ fs.writeFileSync(dest, fileContent)
+ } else if (typeof Blob !== 'undefined' && fileContent instanceof Blob) {
+ const arrayBuffer = await fileContent.arrayBuffer()
+ fs.writeFileSync(dest, Buffer.from(arrayBuffer))
+ } else if (Buffer.isBuffer(fileContent) || fileContent instanceof Uint8Array) {
+ fs.writeFileSync(dest, fileContent)
+ } else {
+ // Fallback: stringify unknown content to aid debugging
+ fs.writeFileSync(dest, String(fileContent ?? ''))
+ }
+ }Run this test twice locally to confirm stale files no longer affect results.
🏁 Script executed:
#!/bin/bash
set -e
# Inspect the test file to check for any existing cleanup logic or setup
sed -n '1,200p' packages/react-generator/test/generator/generateApp.test.js
# Search for use of fs.rmSync or rimraf that might clean the output directory
grep -R "rmSync" -n packages/react-generator
grep -R "rimraf" -n packages/react-generatorLength of output: 2360
Harden file emission in generateApp test
To ensure reliable, maintainable output handling and prevent flaky diffs, apply the following refactors in packages/react-generator/test/generator/generateApp.test.js:
- Pre-clean the entire result directory before writing any files, so stale artifacts can’t affect comparisons.
- Centralize the output root into a single
outRootconstant to avoid path-typos and repetition. - Normalize
filePath(falling back to'') so empty or undefined paths won’t create “undefined” folders. - Support Buffer and Uint8Array content in addition to string and Blob, guarding Blob checks for environments where it isn’t available.
- Provide a fallback branch that stringifies unknown content for easier debugging.
- // 写入文件
- for (const { fileName, path: filePath, fileContent } of genResult) {
- fs.mkdirSync(path.resolve(__dirname, `./result/appdemo01/${filePath}`), { recursive: true })
-
- if (typeof fileContent === 'string') {
- fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), fileContent)
- } else if (fileContent instanceof Blob) {
- const arrayBuffer = await fileContent.arrayBuffer()
- const buffer = Buffer.from(arrayBuffer)
-
- fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), buffer)
- }
- }
+ // 写入文件
+ const outRoot = path.resolve(__dirname, './result/appdemo01')
+ fs.rmSync(outRoot, { recursive: true, force: true })
+ for (const { fileName, path: filePath, fileContent } of genResult) {
+ const outDir = path.join(outRoot, filePath || '')
+ fs.mkdirSync(outDir, { recursive: true })
+ const dest = path.join(outDir, fileName)
+
+ if (typeof fileContent === 'string') {
+ fs.writeFileSync(dest, fileContent)
+ } else if (typeof Blob !== 'undefined' && fileContent instanceof Blob) {
+ const arrayBuffer = await fileContent.arrayBuffer()
+ fs.writeFileSync(dest, Buffer.from(arrayBuffer))
+ } else if (Buffer.isBuffer(fileContent) || fileContent instanceof Uint8Array) {
+ fs.writeFileSync(dest, fileContent)
+ } else {
+ // Fallback: stringify unknown content to aid debugging
+ fs.writeFileSync(dest, String(fileContent ?? ''))
+ }
+ }📝 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 (const { fileName, path: filePath, fileContent } of genResult) { | |
| fs.mkdirSync(path.resolve(__dirname, `./result/appdemo01/${filePath}`), { recursive: true }) | |
| if (typeof fileContent === 'string') { | |
| fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), fileContent) | |
| } else if (fileContent instanceof Blob) { | |
| const arrayBuffer = await fileContent.arrayBuffer() | |
| const buffer = Buffer.from(arrayBuffer) | |
| fs.writeFileSync(path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), buffer) | |
| } | |
| } | |
| // 写入文件 | |
| const outRoot = path.resolve(__dirname, './result/appdemo01') | |
| fs.rmSync(outRoot, { recursive: true, force: true }) | |
| for (const { fileName, path: filePath, fileContent } of genResult) { | |
| const outDir = path.join(outRoot, filePath || '') | |
| fs.mkdirSync(outDir, { recursive: true }) | |
| const dest = path.join(outDir, fileName) | |
| if (typeof fileContent === 'string') { | |
| fs.writeFileSync(dest, fileContent) | |
| } else if (typeof Blob !== 'undefined' && fileContent instanceof Blob) { | |
| const arrayBuffer = await fileContent.arrayBuffer() | |
| fs.writeFileSync(dest, Buffer.from(arrayBuffer)) | |
| } else if (Buffer.isBuffer(fileContent) || fileContent instanceof Uint8Array) { | |
| fs.writeFileSync(dest, fileContent) | |
| } else { | |
| // Fallback: stringify unknown content to aid debugging | |
| fs.writeFileSync(dest, String(fileContent ?? '')) | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/react-generator/test/generator/generateApp.test.js around lines 27
to 39, the test writes output files in a brittle way; update it to (1) define a
single outRoot constant (e.g., const outRoot = path.resolve(__dirname,
'./result/appdemo01')) and use it for all paths, (2) pre-clean the entire
outRoot directory before the loop (fs.rmSync or equivalent with recursive:true,
force:true), (3) normalize filePath to a safe string fallback (filePath =
filePath || '' or path.normalize) so no "undefined" folders are created, (4)
inside the loop support string, Buffer, Uint8Array and Blob (only if typeof Blob
!== 'undefined') branches and write accordingly, and (5) add a final fallback
that stringifies unknown content for easier debugging; ensure all
path.join/path.resolve uses outRoot and path segments consistently.
| 'componentDidMount', | ||
| 'componentWillUnmount', | ||
| 'componentDidUpdate', | ||
| 'componentDidCatch', | ||
| "console.log('Component mounted')", | ||
| "console.log('Component unmounted')", | ||
| "console.log('Component updated')", | ||
| "console.log('Error caught:', error)" | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make assertions quote-agnostic to avoid brittle failures.
The input schemas use double quotes inside JSFunction strings, while formatters may output single quotes. Asserting on exact console.log('...') will flake. Prefer regex that accepts either quote.
- "console.log('Component mounted')",
- "console.log('Component unmounted')",
- "console.log('Component updated')",
- "console.log('Error caught:', error)"
+ /console\.log\((["'])Component mounted\1\)/,
+ /console\.log\((["'])Component unmounted\1\)/,
+ /console\.log\((["'])Component updated\1\)/,
+ /console\.log\((["'])Error caught:\1,\s*error\)/
@@
- assertions: ['handleClick', "console.log('Button clicked')"]
+ assertions: ['handleClick', /console\.log\((["'])Button clicked\1\)/]Also applies to: 53-54
🤖 Prompt for AI Agents
In packages/react-generator/test/lifecycle/lifecycle.test.js around lines 40 to
48 (and also adjust 53-54), the test asserts exact console.log strings using
single quotes which is brittle because formatters may emit double quotes;
replace those strict string equality assertions with regex-based assertions that
accept either single or double quotes (e.g., use a pattern matching
console.log\(['"]...['"]\) and assert with .toMatch or
.toEqual(expect.stringMatching(...)) depending on test framework) for the
mounted, unmounted, updated and error log expectations so the tests are
quote-agnostic.
| testCase.assertions.forEach((assertion) => { | ||
| expect(jsxFile.panelValue).toContain(assertion) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Support RegExp assertions in the generic runner.
With the above regex changes, update the assertion loop to handle both strings and RegExp:
- testCase.assertions.forEach((assertion) => {
- expect(jsxFile.panelValue).toContain(assertion)
- })
+ testCase.assertions.forEach((assertion) => {
+ if (assertion instanceof RegExp) {
+ expect(jsxFile.panelValue).toMatch(assertion)
+ } else {
+ expect(jsxFile.panelValue).toContain(assertion)
+ }
+ })📝 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.
| testCase.assertions.forEach((assertion) => { | |
| expect(jsxFile.panelValue).toContain(assertion) | |
| }) | |
| testCase.assertions.forEach((assertion) => { | |
| if (assertion instanceof RegExp) { | |
| expect(jsxFile.panelValue).toMatch(assertion) | |
| } else { | |
| expect(jsxFile.panelValue).toContain(assertion) | |
| } | |
| }) |
🤖 Prompt for AI Agents
In packages/react-generator/test/lifecycle/lifecycle.test.js around lines 124 to
126, the assertion loop currently assumes every assertion is a string and uses
toContain; update it to handle both strings and RegExp by checking if assertion
is an instance of RegExp and, if so, use the appropriate Jest matcher (e.g.,
toMatch) against jsxFile.panelValue, otherwise keep using toContain for string
assertions.
| methods: { | ||
| focusInput: { | ||
| type: 'JSFunction', | ||
| value: 'function focusInput() { console.log("Focusing input"); }' | ||
| }, | ||
| handleSubmit: { | ||
| type: 'JSFunction', | ||
| value: 'function handleSubmit() { console.log("Submitting:", this.state.inputValue); }' | ||
| }, | ||
| incrementCount: { | ||
| type: 'JSFunction', | ||
| value: 'function incrementCount() { this.setState(prev => ({ count: prev.count + 1 })); }' | ||
| }, | ||
| toggleVisibility: { | ||
| type: 'JSFunction', | ||
| value: 'function toggleVisibility() { this.setState(prev => ({ isVisible: !prev.isVisible })); }' | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing method: mixedLifecycleSchema references handleInputChange but does not define it
TinyInput.onChange binds to this.handleInputChange, which will be undefined at runtime and in generated code. Add the method to keep the schema self-consistent.
Apply this diff to add the missing method:
methods: {
focusInput: {
type: 'JSFunction',
value: 'function focusInput() { console.log("Focusing input"); }'
},
+ handleInputChange: {
+ type: 'JSFunction',
+ value: 'function handleInputChange(e) { this.setState({ inputValue: e.target.value }); }'
+ },
handleSubmit: {
type: 'JSFunction',
value: 'function handleSubmit() { console.log("Submitting:", this.state.inputValue); }'
},Also applies to: 219-223
🤖 Prompt for AI Agents
In packages/react-generator/test/lifecycle/mockData.js around lines 171-188 (and
similarly at 219-223) the schema references this.handleInputChange from
TinyInput.onChange but no handleInputChange method is defined; add a new method
entry named handleInputChange with type 'JSFunction' and a value implementing
the expected handler (e.g., accepting event, updating state/input value or
calling setState appropriately) so generated code has the method present and
bindings resolve at runtime; ensure its shape and naming match existing method
entries so both occurrences are updated.
| componentName: 'div', | ||
| props: { | ||
| style: { | ||
| type: 'JSExpression', | ||
| value: 'this.state.isVisible ? "block" : "none"' | ||
| } | ||
| }, | ||
| children: ['这是可切换的内容'], | ||
| id: 'toggle-content' | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid style binding: style expects an object, but the expression returns a string
style should be an object with CSS properties. Returning "block"/"none" will produce an invalid style attribute in JSX. Bind display explicitly.
Apply this diff:
- props: {
- style: {
- type: 'JSExpression',
- value: 'this.state.isVisible ? "block" : "none"'
- }
- },
+ props: {
+ style: {
+ type: 'JSExpression',
+ value: '({ display: this.state.isVisible ? "block" : "none" })'
+ }
+ },Also applies to: 279-283
🤖 Prompt for AI Agents
In packages/react-generator/test/lifecycle/mockData.js around lines 277 to 286
(also applies to 279-283), the style binding currently returns a string
("block"/"none") which is invalid for JSX style (expects an object); change the
expression to return an object with an explicit display property (e.g., {
display: this.state.isVisible ? 'block' : 'none' }) so the style prop is a
proper object of CSS properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
| let curHookName = '' | ||
|
|
||
| try { | ||
| await this.transformStart() | ||
| await this.transform() | ||
| } catch (error) { | ||
| this.error.push(error) | ||
|
|
||
| if (!this.tolerateError) { | ||
| throw new Error( | ||
| `[codeGenerator][generate] get error when running hook: ${curHookName}. error message: ${JSON.stringify( | ||
| error | ||
| )}` | ||
| ) | ||
| } | ||
| } finally { | ||
| await this.transformEnd() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix empty hook name in thrown error and standardize error records with hook and plugin metadata
- curHookName is never updated, so the thrown message lacks the hook name.
- Error records have inconsistent shapes (raw Error vs object with message/stack).
- pluginItem.name may be undefined; add a safe fallback.
Set curHookName before each phase, include hook in error records, and normalize plugin name. This makes failures actionable and test-friendly.
- let curHookName = ''
+ let curHookName = ''
try {
- await this.transformStart()
- await this.transform()
+ curHookName = 'transformStart'
+ await this.transformStart()
+ curHookName = 'transform'
+ await this.transform()
} catch (error) {
- this.error.push(error)
+ this.error.push({ message: error.message, stack: error.stack, hook: curHookName })
if (!this.tolerateError) {
throw new Error(
`[codeGenerator][generate] get error when running hook: ${curHookName}. error message: ${JSON.stringify(
error
)}`
)
}
} finally {
- await this.transformEnd()
+ curHookName = 'transformEnd'
+ await this.transformEnd()
}And in each stage, add hook and safe plugin name:
- } catch (error) {
- const err = { message: error.message, stack: error.stack, plugin: pluginItem.name }
+ } catch (error) {
+ const pluginName = pluginItem.name || pluginItem.constructor?.name || 'anonymous'
+ const err = { message: error.message, stack: error.stack, plugin: pluginName, hook: 'transformStart' }
this.error.push(err)
if (!this.tolerateError) {
- throw new Error(`[${pluginItem.name}] throws error`, { cause: error })
+ throw new Error(`[${pluginName}] throws error`, { cause: error })
}
}Repeat the same pattern for transform (hook: 'transform') and transformEnd (hook: 'transformEnd').
Also applies to: 79-88, 97-116, 125-134
🤖 Prompt for AI Agents
In packages/react-generator/src/generator/codeGenerator.js around lines 44 to 61
(and also apply the same changes at 79-88, 97-116, 125-134), curHookName is
never set so thrown errors lack the hook context and error records are
inconsistent and missing plugin/hook metadata; before each stage
(transformStart, transform, transformEnd) set curHookName to the corresponding
hook string, normalize pluginItem.name to a safe fallback (e.g., pluginItem.name
|| '<unknown plugin>'), and when catching errors push a normalized error object
({ message: error.message || String(error), stack: error.stack || '', plugin:
safeName, hook: curHookName }) into this.error and use that same curHookName in
the thrown Error message so logs consistently include hook and plugin
information.
| if (item.params?.length) { | ||
| const extendParams = item.params.join(',') | ||
| return `${key}={(...eventArgs) => ${eventHandler}(eventArgs, ${extendParams})}` | ||
| } else { | ||
| return `${key}={${eventHandler}}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix event handler wrapper passing an array instead of individual args
Currently eventArgs is passed as a single array argument. Spread it to preserve the original handler signature.
- const extendParams = item.params.join(',')
- return `${key}={(...eventArgs) => ${eventHandler}(eventArgs, ${extendParams})}`
+ const extendParams = item.params.join(',')
+ return `${key}={(...eventArgs) => ${eventHandler}(...eventArgs${extendParams ? `, ${extendParams}` : ''})}`📝 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.
| if (item.params?.length) { | |
| const extendParams = item.params.join(',') | |
| return `${key}={(...eventArgs) => ${eventHandler}(eventArgs, ${extendParams})}` | |
| } else { | |
| return `${key}={${eventHandler}}` | |
| if (item.params?.length) { | |
| const extendParams = item.params.join(',') | |
| return `${key}={(...eventArgs) => ${eventHandler}(...eventArgs${extendParams ? `, ${extendParams}` : ''})}` | |
| } else { | |
| return `${key}={${eventHandler}}` | |
| } |
🤖 Prompt for AI Agents
In packages/react-generator/src/generator/page.js around lines 147 to 151, the
generated wrapper currently passes the eventArgs array as a single argument to
the original handler; change the wrapper so it spreads eventArgs when calling
the handler (i.e. use ...eventArgs when invoking the handler) while still
appending the extendParams variables after the spread so the original handler
receives individual event args followed by the extra params.
| if (item?.componentType === BUILTIN_COMPONENT_NAME.BLOCK) { | ||
| blocks.push(item?.componentName) | ||
| } else { | ||
| components.push(item?.componentName) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Detect blocks by componentName, not componentType
The DSL uses BUILTIN_COMPONENT_NAME.BLOCK as a componentName. Checking componentType is unreliable here.
- if (item?.componentType === BUILTIN_COMPONENT_NAME.BLOCK) {
+ if (item?.componentName === BUILTIN_COMPONENT_NAME.BLOCK) {
blocks.push(item?.componentName)
} else {
components.push(item?.componentName)
}📝 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.
| if (item?.componentType === BUILTIN_COMPONENT_NAME.BLOCK) { | |
| blocks.push(item?.componentName) | |
| } else { | |
| components.push(item?.componentName) | |
| } | |
| if (item?.componentName === BUILTIN_COMPONENT_NAME.BLOCK) { | |
| blocks.push(item?.componentName) | |
| } else { | |
| components.push(item?.componentName) | |
| } |
🤖 Prompt for AI Agents
In packages/react-generator/src/generator/parseImport.js around lines 9 to 13,
the code currently detects blocks by checking item.componentType which is
unreliable; change the condition to check item.componentName ===
BUILTIN_COMPONENT_NAME.BLOCK (use strict equality and guard null/undefined as
needed) and push item.componentName into blocks when it matches, otherwise push
into components; preserve existing optional chaining/guards to avoid runtime
errors.
| const { blockRelativePath = '../components', blockSuffix = '.vue' } = config | ||
| const blockPkgMap = {} | ||
| const relativePath = blockRelativePath.endsWith('/') ? blockRelativePath.slice(0, -1) : blockRelativePath | ||
|
|
||
| blocks.map((name) => { | ||
| const source = `${relativePath}/${name}${blockSuffix}` | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use React-appropriate block suffix ('.jsx' or configurable), not '.vue'
Defaults here are Vue leftovers and will produce incorrect paths in React output.
- const { blockRelativePath = '../components', blockSuffix = '.vue' } = config
+ const { blockRelativePath = '../components', blockSuffix = '.jsx' } = config📝 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 { blockRelativePath = '../components', blockSuffix = '.vue' } = config | |
| const blockPkgMap = {} | |
| const relativePath = blockRelativePath.endsWith('/') ? blockRelativePath.slice(0, -1) : blockRelativePath | |
| blocks.map((name) => { | |
| const source = `${relativePath}/${name}${blockSuffix}` | |
| const { blockRelativePath = '../components', blockSuffix = '.jsx' } = config | |
| const blockPkgMap = {} | |
| const relativePath = blockRelativePath.endsWith('/') ? blockRelativePath.slice(0, -1) : blockRelativePath | |
| blocks.map((name) => { | |
| const source = `${relativePath}/${name}${blockSuffix}` |
🤖 Prompt for AI Agents
In packages/react-generator/src/generator/parseImport.js around lines 45 to 51,
the default blockSuffix is set to '.vue' (a Vue artifact) which yields incorrect
import paths for React; change the destructured default from '.vue' to a
React-appropriate default such as '.jsx' (or '.tsx' if TS support is expected),
ensure the value remains overridable via config, and update any callers/tests
that relied on the old default to use the new default or explicitly set the
suffix.
| const { blockRelativePath = '../components/', blockSuffix = '.vue' } = config | ||
|
|
||
| const importComps = componentsMap.filter(({ componentName }) => components.includes(componentName)) | ||
|
|
||
| importComps.forEach((item) => { | ||
| pkgMap[item.package] = pkgMap[item.package] || [] | ||
|
|
||
| pkgMap[item.package].push(item) | ||
| }) | ||
|
|
||
| const batchImportStatements = Object.entries(pkgMap).map(([key, value]) => { | ||
| return generateImportByPkgName({ pkgName: key, imports: value }) | ||
| }) | ||
|
|
||
| const blockImportStatement = blocks.map((name) => { | ||
| return `import ${name} from ${blockRelativePath}/${name}${blockSuffix}` | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix generated import syntax and path joining for blocks
The import path must be a string literal and should avoid double slashes.
- const { blockRelativePath = '../components/', blockSuffix = '.vue' } = config
+ const { blockRelativePath = '../components', blockSuffix = '.jsx' } = config
@@
- const blockImportStatement = blocks.map((name) => {
- return `import ${name} from ${blockRelativePath}/${name}${blockSuffix}`
- })
+ const rel = blockRelativePath.endsWith('/') ? blockRelativePath.slice(0, -1) : blockRelativePath
+ const blockImportStatement = blocks.map((name) => {
+ return `import ${name} from '${rel}/${name}${blockSuffix}'`
+ })📝 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 { blockRelativePath = '../components/', blockSuffix = '.vue' } = config | |
| const importComps = componentsMap.filter(({ componentName }) => components.includes(componentName)) | |
| importComps.forEach((item) => { | |
| pkgMap[item.package] = pkgMap[item.package] || [] | |
| pkgMap[item.package].push(item) | |
| }) | |
| const batchImportStatements = Object.entries(pkgMap).map(([key, value]) => { | |
| return generateImportByPkgName({ pkgName: key, imports: value }) | |
| }) | |
| const blockImportStatement = blocks.map((name) => { | |
| return `import ${name} from ${blockRelativePath}/${name}${blockSuffix}` | |
| }) | |
| const { blockRelativePath = '../components', blockSuffix = '.jsx' } = config | |
| const importComps = componentsMap.filter(({ componentName }) => components.includes(componentName)) | |
| importComps.forEach((item) => { | |
| pkgMap[item.package] = pkgMap[item.package] || [] | |
| pkgMap[item.package].push(item) | |
| }) | |
| const batchImportStatements = Object.entries(pkgMap).map(([key, value]) => { | |
| return generateImportByPkgName({ pkgName: key, imports: value }) | |
| }) | |
| const rel = blockRelativePath.endsWith('/') ? blockRelativePath.slice(0, -1) : blockRelativePath | |
| const blockImportStatement = blocks.map((name) => { | |
| return `import ${name} from '${rel}/${name}${blockSuffix}'` | |
| }) |
🤖 Prompt for AI Agents
In packages/react-generator/src/generator/parseImport.js around lines 70 to 86,
the generated block import statements produce invalid JS because the import path
is not a quoted string and can contain double slashes; update the block import
generation to produce a quoted string literal and normalize the path to avoid
duplicate slashes. Specifically, ensure you wrap the final path in single (or
double) quotes in the import statement and construct the path by trimming any
trailing slash from blockRelativePath (or trimming a leading slash from the
block fragment) before concatenating with "/" + name + blockSuffix so you never
get "//" and the import becomes e.g. import Component from
'normalized/path/Component.suffix'.
| const errorHandler = (error) => { | ||
| config.errorHandler?.value && createFn(config.errorHandler.value)(error) | ||
| dataSource.status = 'error' | ||
| dataSource.error = error | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Propagate errors from interceptors
errorHandler swallows errors, turning rejections into resolved undefined and hiding failures from callers. It must rethrow (return Promise.reject(error)).
Apply this diff:
- const errorHandler = (error) => {
- config.errorHandler?.value && createFn(config.errorHandler.value)(error)
- dataSource.status = 'error'
- dataSource.error = error
- }
+ const errorHandler = (error) => {
+ try {
+ config.errorHandler?.value && createFn(config.errorHandler.value)(error)
+ } finally {
+ dataSource.status = 'error'
+ dataSource.error = error
+ }
+ return Promise.reject(error)
+ }📝 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 errorHandler = (error) => { | |
| config.errorHandler?.value && createFn(config.errorHandler.value)(error) | |
| dataSource.status = 'error' | |
| dataSource.error = error | |
| } | |
| const errorHandler = (error) => { | |
| try { | |
| config.errorHandler?.value && createFn(config.errorHandler.value)(error) | |
| } finally { | |
| dataSource.status = 'error' | |
| dataSource.error = error | |
| } | |
| return Promise.reject(error) | |
| } |
🤖 Prompt for AI Agents
In
packages/react-generator/test/generator/expected/appdemo01/src/lowcodeConfig/dataSource.js
around lines 74 to 78, the errorHandler currently logs/records the error but
swallows it which converts rejections into resolved undefined; update it to
propagate the error by rethrowing or returning Promise.reject(error) after
calling the configured handler so callers still receive the rejection (e.g.,
call the config handler, set status/error, then either throw error or return
Promise.reject(error)).
| <Typography variant="h1" style={{ marginBottom: '20px' }}> | ||
| React Demo Page | ||
| </Typography> | ||
| <Input |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Ant Design Typography API usage.
variant isn’t an Ant Design prop. Use Typography.Title and Typography.Text (or Paragraph).
Apply:
- <Typography variant="h1" style={{ marginBottom: '20px' }}>
- React Demo Page
- </Typography>
+ <Typography.Title level={1} style={{ marginBottom: '20px' }}>
+ React Demo Page
+ </Typography.Title>
@@
- <Typography variant="body1">{'输入内容: ' + state.inputValue}</Typography>
+ <Typography.Text>{'输入内容: ' + state.inputValue}</Typography.Text>Also applies to: 46-46
🤖 Prompt for AI Agents
packages/react-generator/test/generator/expected/appdemo01/src/views/DemoPage/DemoPage.jsx
around lines 34-37 and 46: The code uses a non-existent Ant Design prop
`variant` on Typography; replace usage with Ant Design components and props: use
Typography.Title (e.g. <Typography.Title level={1} style={{ marginBottom: '20px'
}}>React Demo Page</Typography.Title>) for headings and use <Typography.Text> or
<Typography.Paragraph> for body text instead of <Typography variant="...">;
remove the invalid `variant` prop and adjust imports to reference
Typography.Title / Typography.Text (or Paragraph) as needed.
| options: [ | ||
| { | ||
| test: 'test1', | ||
| test1: 'test1', | ||
| _id: '341efc48' | ||
| }, | ||
| { | ||
| test: 'test2', | ||
| test1: 'test1', | ||
| _id: 'b86b516c' | ||
| }, | ||
| { | ||
| test: 'test3', | ||
| test1: 'test1', | ||
| _id: 'f680cd78' | ||
| } | ||
| ], | ||
| options: { | ||
| uri: '', | ||
| method: 'GET' | ||
| }, | ||
| dataHandler: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate object keys: options is defined twice; earlier value is overwritten
In getAllList.options, options appears as both an array and a request config object. Only the last one survives, causing data loss.
Recommend renaming the request config to request (or similar) to avoid collisions:
],
- options: [
+ options: [
{
test: 'test1',
test1: 'test1',
_id: '341efc48'
},
{
test: 'test2',
test1: 'test1',
_id: 'b86b516c'
},
{
test: 'test3',
test1: 'test1',
_id: 'f680cd78'
}
],
- options: {
+ request: {
uri: '',
method: 'GET'
},If downstream code expects options for request config, I can update the generator/parser accordingly. Should I propagate this change?
📝 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.
| options: [ | |
| { | |
| test: 'test1', | |
| test1: 'test1', | |
| _id: '341efc48' | |
| }, | |
| { | |
| test: 'test2', | |
| test1: 'test1', | |
| _id: 'b86b516c' | |
| }, | |
| { | |
| test: 'test3', | |
| test1: 'test1', | |
| _id: 'f680cd78' | |
| } | |
| ], | |
| options: { | |
| uri: '', | |
| method: 'GET' | |
| }, | |
| dataHandler: { | |
| options: [ | |
| { | |
| test: 'test1', | |
| test1: 'test1', | |
| _id: '341efc48' | |
| }, | |
| { | |
| test: 'test2', | |
| test1: 'test1', | |
| _id: 'b86b516c' | |
| }, | |
| { | |
| test: 'test3', | |
| test1: 'test1', | |
| _id: 'f680cd78' | |
| } | |
| ], | |
| request: { | |
| uri: '', | |
| method: 'GET' | |
| }, | |
| dataHandler: { | |
| // …rest of the config | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 38-55: This property is later overwritten by an object member with the same name.
Overwritten with this property.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.
(lint/suspicious/noDuplicateObjectKeys)
🤖 Prompt for AI Agents
In packages/react-generator/test/generator/mockData.js around lines 38 to 59,
the object has duplicate keys named "options" (first an array, then an object)
so the array is overwritten; rename the second "options" (the request config) to
a distinct key such as "request" or "requestOptions" and update any
test/generator code that reads this mock to use the new key name so there are no
collisions and the original options array remains intact.
| id: 86, | ||
| username: '开发者', | ||
| email: '[email protected]', | ||
| confirmationToken: 'dfb2c162-351f-4f44-ad5f-8998', | ||
| is_admin: true | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Gitleaks: replace hard-coded tokens with safe placeholders
confirmationToken values look like tokens and trigger secret scanners. They’re test data—mask them.
- confirmationToken: 'dfb2c162-351f-4f44-ad5f-8998',
+ confirmationToken: 'PLACEHOLDER_TOKEN',Repeat for the second occurrence around the createVm page meta.
Also applies to: 1424-1428
🧰 Tools
🪛 Gitleaks (8.27.2)
336-336: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
In packages/react-generator/test/generator/mockData.js around lines 333-338 (and
similarly at the createVm page meta and lines 1424-1428), the confirmationToken
fields use hard-coded UUID-like values that trigger secret scanners; replace
these literal token-looking values with non-secret placeholders (e.g.,
'CONFIRMATION_TOKEN_PLACEHOLDER' or a clearly non-UUID test string) in all
occurrences so tests still have a value but no real/UUID-like tokens are
present.
| labelCol: '80px', | ||
| layout: 'top', | ||
| layout: false, | ||
| 'label-position': 'left ', | ||
| 'label-width': '150px', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overwritten props: duplicate “layout” in Form props
layout: 'top' and layout: false coexist; the latter overwrites the former silently.
Drop the duplicate (or choose the intended value):
- layout: 'top',
- layout: false,
+ layout: 'top',Apply the same fix to the later Form at Lines 1096-1101.
Also applies to: 1096-1101
🤖 Prompt for AI Agents
In packages/react-generator/test/generator/mockData.js around lines 882-886, the
Form props include duplicate "layout" entries (layout: 'top' and layout: false)
causing the latter to overwrite the former; remove the unintended duplicate so
only the intended layout remains (choose either 'top' or false based on desired
UI) and ensure the same fix is applied to the other Form instance at lines
1096-1101 so neither Form has conflicting duplicate "layout" props.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
| constructor(config) { | ||
| this.config = config | ||
| this.plugins = config.plugins | ||
| this.context = { | ||
| ...this.context, | ||
| ...(this.config.context || {}) | ||
| } | ||
|
|
||
| if (typeof config.tolerateError === 'boolean') { | ||
| this.tolerateError = config.tolerateError | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Defensive defaults for plugin arrays to prevent undefined access
Accessing this.plugins.transformStart/transform/transformEnd assumes all are present. Provide safe defaults in the constructor so consumers can omit stages without crashing.
constructor(config) {
- this.config = config
- this.plugins = config.plugins
+ this.config = config
+ const { plugins = {} } = config
+ this.plugins = {
+ transformStart: Array.isArray(plugins.transformStart) ? plugins.transformStart : [],
+ transform: Array.isArray(plugins.transform) ? plugins.transform : [],
+ transformEnd: Array.isArray(plugins.transformEnd) ? plugins.transformEnd : []
+ }
this.context = {
...this.context,
...(this.config.context || {})
}
if (typeof config.tolerateError === 'boolean') {
this.tolerateError = config.tolerateError
}
}📝 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.
| constructor(config) { | |
| this.config = config | |
| this.plugins = config.plugins | |
| this.context = { | |
| ...this.context, | |
| ...(this.config.context || {}) | |
| } | |
| if (typeof config.tolerateError === 'boolean') { | |
| this.tolerateError = config.tolerateError | |
| } | |
| } | |
| constructor(config) { | |
| this.config = config | |
| const { plugins = {} } = config | |
| this.plugins = { | |
| transformStart: Array.isArray(plugins.transformStart) ? plugins.transformStart : [], | |
| transform: Array.isArray(plugins.transform) ? plugins.transform : [], | |
| transformEnd: Array.isArray(plugins.transformEnd) ? plugins.transformEnd : [] | |
| } | |
| this.context = { | |
| ...this.context, | |
| ...(this.config.context || {}) | |
| } | |
| if (typeof config.tolerateError === 'boolean') { | |
| this.tolerateError = config.tolerateError | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/react-generator/src/generator/codeGenerator.js around lines 17 to
28, the constructor assumes this.plugins and its plugin stage arrays exist which
can cause runtime errors; set defensive defaults by ensuring this.plugins is at
least an object and each stage (transformStart, transform, transformEnd) is an
array if missing. Update the constructor to initialize this.plugins =
config.plugins || {} (or shallow-clone it) and then guarantee
this.plugins.transformStart = this.plugins.transformStart || [],
this.plugins.transform = this.plugins.transform || [], and
this.plugins.transformEnd = this.plugins.transformEnd || []; keep existing
tolerateError and context logic unchanged.
| const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1) | ||
| // 本地实现 hyphenate | ||
| const hyphenate = (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid leading hyphen in hyphenate('ClickAway') -> 'click-away'
The current hyphenate adds a leading '-' for initial capitals (e.g., 'Click' -> '-click'). Use a non-boundary lookbehind to skip the first character.
-const hyphenate = (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase()
+const hyphenate = (str) => str.replace(/\B([A-Z])/g, '-$1').toLowerCase()toEventKey will then correctly produce 'click-away' from 'onClickAway'.
Also applies to: 95-103
🤖 Prompt for AI Agents
In packages/react-generator/src/utils/index.js around lines 15 to 17 (and
similarly at lines 95-103), the hyphenate implementation inserts a leading
hyphen for initial capitals (e.g., 'Click' -> '-click'); change the regex logic
so it only inserts hyphens before capital letters that are not at the start of
the string — for example use a pattern or check that skips the first character
(or use a lookbehind / index check) so 'ClickAway' becomes 'click-away' and
'onClickAway' -> 'click-away' via toEventKey.
| const getFunctionInfo = (fnStr) => { | ||
| const fnRegexp = /(async)?.*?(\w+) *\(([\s\S]*?)\) *\{([\s\S]*)\}/ | ||
| const result = fnRegexp.exec(fnStr) | ||
| if (result) { | ||
| return { | ||
| type: result[1] || '', | ||
| name: result[2], | ||
| params: result[3] | ||
| .split(',') | ||
| .map((item) => item.trim()) | ||
| .filter((item) => Boolean(item)), | ||
| body: result[4] | ||
| } | ||
| } | ||
| return null | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
getFunctionInfo returns null on non-matches; consumers may destructure without guards
parser/state-type.js destructures getFunctionInfo(value) without a null-check in the provided snippet. This will throw if a method is an arrow function or otherwise not matched by the regex. Either ensure callers guard or make this utility throw with a clearer message for invalid inputs.
🏁 Script executed:
#!/bin/bash
# Inspect all call sites to confirm whether name/params/body are always guarded
rg -n -C3 -g "packages/react-generator/**" -P "getFunctionInfo\\("Length of output: 2486
Add null-check or enforce non-null return for getFunctionInfo to prevent runtime errors
getFunctionInfo may return null when fnStr doesn’t match the regex (for example, with arrow functions or uncommon signatures). At several call sites—including
- packages/react-generator/src/parser/state-type.js (line 28)
- packages/react-generator/src/generator/page.js (lines 504, 522, 589)—
the code immediately destructures or spreads its return value without checking for null. This will produce a runtime exception if getFunctionInfo returns null.
Suggestions:
- Guard at call sites: check the return value before destructuring or spreading.
- Or change getFunctionInfo to throw a descriptive error on invalid input, or return a safe default object (e.g.
{ type: '', name: '', params: [], body: '' }) so destructuring never fails.
Please address these spots to ensure invalid fnStr inputs can’t crash the generator.
🤖 Prompt for AI Agents
In packages/react-generator/src/utils/index.js around lines 24 to 39,
getFunctionInfo currently returns null when the regex doesn't match which causes
runtime destructuring errors at multiple call sites; change its behavior to
always return a safe default object instead of null (e.g. { type: '', name: '',
params: [], body: '' }) and ensure params is an array (empty if no params) so
callers can destructure/spread without null-checks; alternatively you may throw
a descriptive error, but the preferred quick fix is to replace the final return
null with the safe default and ensure the parsed params code yields [] when
input is empty.
| const safeRandom = () => { | ||
| const mathConstructor = Math | ||
|
|
||
| return mathConstructor.random | ||
| } | ||
|
|
||
| const randomString = (length = 4, chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') => { | ||
| let result = '' | ||
| for (let i = length; i > 0; --i) { | ||
| result += chars[Math.floor(safeRandom() * chars.length)] | ||
| } | ||
| return result | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: safeRandom returns the function, not a number — randomString yields NaN indices
safeRandom currently returns Math.random (function). randomString calls safeRandom() expecting a number. This produces NaN and 'undefined' characters in the result.
-const safeRandom = () => {
- const mathConstructor = Math
-
- return mathConstructor.random
-}
+const safeRandom = () => {
+ const { random } = Math
+ return random()
+}📝 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 safeRandom = () => { | |
| const mathConstructor = Math | |
| return mathConstructor.random | |
| } | |
| const randomString = (length = 4, chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') => { | |
| let result = '' | |
| for (let i = length; i > 0; --i) { | |
| result += chars[Math.floor(safeRandom() * chars.length)] | |
| } | |
| return result | |
| } | |
| const safeRandom = () => { | |
| const { random } = Math | |
| return random() | |
| } | |
| const randomString = (length = 4, chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') => { | |
| let result = '' | |
| for (let i = length; i > 0; --i) { | |
| result += chars[Math.floor(safeRandom() * chars.length)] | |
| } | |
| return result | |
| } |
🤖 Prompt for AI Agents
In packages/react-generator/src/utils/index.js around lines 41 to 53, safeRandom
currently returns the Math.random function itself instead of a number, so
randomString calls safeRandom() and gets a function which leads to NaN when used
as a multiplier; fix by changing safeRandom to return a numeric random value
(e.g., call Math.random() or use a secure fallback like crypto.getRandomValues
to produce a number), and ensure randomString uses that numeric return value;
keep the simple change to invoke Math.random() in safeRandom and optionally add
a fallback that returns Math.random() if secure RNG isn’t available.
| const addIconRecord = (description, componentName) => { | ||
| const { componentNames, exportNames } = description.iconComponents | ||
|
|
||
| if (!componentNames.includes(componentName)) { | ||
| const exportName = componentName.replace(TINY_ICON, 'icon') | ||
|
|
||
| componentNames.push(componentName) | ||
| exportNames.push(exportName) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Icon export name derivation depends on TINY_ICON; current constant is empty -> wrong results
Per the provided snippet, TINY_ICON is set to an empty string, so:
- componentName.replace(TINY_ICON, 'icon') will prefix 'icon' to any name, producing invalid export names like 'iconTinyIconHelpCircle'.
- pre-processor startsWith(TINY_ICON) is always true, bypassing intended prefixing.
Either fix TINY_ICON (likely 'TinyIcon') or make addIconRecord robust to an empty prefix.
Proposed defensive change:
- if (!componentNames.includes(componentName)) {
- const exportName = componentName.replace(TINY_ICON, 'icon')
+ if (!componentNames.includes(componentName)) {
+ const prefix = TINY_ICON || 'TinyIcon'
+ const exportName = componentName.startsWith(prefix)
+ ? componentName.replace(prefix, 'icon')
+ : `icon${componentName.replace(/^Tiny/, '')}`And please verify the actual constant and pre-processor behavior:
🏁 Script executed:
#!/bin/bash
# Confirm TINY_ICON value and all code paths using it
rg -n -C2 -g "packages/react-generator/**" -P "TINY_ICON|tinyIcon\\s+as\\s+unifyIconName"
# Show the constant definition
sed -n '1,220p' packages/react-generator/src/constant/index.js | nl -ba | sed -n '1,220p' | rg -n "TINY_ICON"Length of output: 4502
Fix empty TINY_ICON causing invalid icon export names
TINY_ICON is currently defined as an empty string (const TINY_ICON = '' in packages/react-generator/src/constant/index.js:398), which means
- In addIconRecord (packages/react-generator/src/utils/index.js:127–136),
componentName.replace(TINY_ICON, 'icon')always prefixes “icon” (e.g. “iconTinyIconHelpCircle”) instead of stripping only the TinyIcon prefix. - In parser/jsx-slot.js (lines 72–76) and pre-processor/index.js (lines 33–37),
componentName.startsWith(TINY_ICON)is always true, bypassing the intended logic.
Locations to update:
- constant/index.js: line 398 (
const TINY_ICON = '') - utils/index.js: addIconRecord logic
- pre-processor/index.js & parser/jsx-slot.js: startsWith guards
Suggested change in addIconRecord:
const addIconRecord = (description, componentName) => {
const { componentNames, exportNames } = description.iconComponents
if (!componentNames.includes(componentName)) {
- const exportName = componentName.replace(TINY_ICON, 'icon')
+ const exportName = TINY_ICON && componentName.startsWith(TINY_ICON)
+ ? componentName.replace(TINY_ICON, 'icon')
+ : `icon${componentName.replace(/^Tiny/, '')}`
componentNames.push(componentName)
exportNames.push(exportName)
}
}Also guard empty prefixes in parser/pre-processor:
- if (componentName?.startsWith(TINY_ICON)) {
+ if (TINY_ICON && componentName?.startsWith(TINY_ICON)) {Alternatively, restore the intended prefix by setting TINY_ICON = 'TinyIcon' once TinyIcon support is re-enabled.
📝 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 addIconRecord = (description, componentName) => { | |
| const { componentNames, exportNames } = description.iconComponents | |
| if (!componentNames.includes(componentName)) { | |
| const exportName = componentName.replace(TINY_ICON, 'icon') | |
| componentNames.push(componentName) | |
| exportNames.push(exportName) | |
| } | |
| } | |
| const addIconRecord = (description, componentName) => { | |
| const { componentNames, exportNames } = description.iconComponents | |
| if (!componentNames.includes(componentName)) { | |
| const exportName = TINY_ICON && componentName.startsWith(TINY_ICON) | |
| ? componentName.replace(TINY_ICON, 'icon') | |
| : `icon${componentName.replace(/^Tiny/, '')}` | |
| componentNames.push(componentName) | |
| exportNames.push(exportName) | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/react-generator/src/utils/index.js around lines 127–136, update
addIconRecord so it only strips the TinyIcon prefix when a non-empty TINY_ICON
is defined and the componentName actually starts with that prefix: compute
exportName by checking if (TINY_ICON && componentName.startsWith(TINY_ICON))
then use componentName.replace(TINY_ICON, 'icon') else use componentName as-is;
push the exportName unchanged otherwise. Also update
packages/react-generator/src/constant/index.js (line 398) to either restore the
intended prefix (e.g. const TINY_ICON = 'TinyIcon') or leave it empty but ensure
callers handle an empty prefix. Finally, in pre-processor/index.js and
parser/jsx-slot.js wrap startsWith checks with a guard so they only call
componentName.startsWith(TINY_ICON) when TINY_ICON is non-empty (e.g. if
(TINY_ICON && componentName.startsWith(TINY_ICON)) { ... }).
| "label": "深'i'圳", | ||
| "value": 1 | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Great edge case for string escaping ('深'i'圳') — ensure codegen escapes single quotes under singleQuote formatting
Prettier is configured with singleQuote=true. When this label is emitted as a JS string literal, single quotes must be escaped to avoid broken code. If your literal emission relies on JSON.stringify or a custom escaper, verify it preserves/escapes quotes correctly under the chosen quote style.
To verify coverage, please search for an explicit test around single-quote escaping in emitted code:
🏁 Script executed:
#!/bin/bash
# Locate generator code that stringifies/prints literal props and any escaping utilities
rg -n -C3 -g "packages/react-generator/**" -P "(escape.*string|stringif|quote|literal).*"
# Ensure emitted snapshots include escaped single quotes
rg -n -C2 -g "packages/react-generator/test/**" -P "深.?i.?圳|\\'"Length of output: 308071
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- parser/jsx-slot.js (full) ----"
sed -n '1,240p' packages/react-generator/src/parser/jsx-slot.js || true
echo
echo "---- generator/page.js (1-260) ----"
sed -n '1,260p' packages/react-generator/src/generator/page.js || true
echo
echo "---- generator/page.js (260-520) ----"
sed -n '260,520p' packages/react-generator/src/generator/page.js || true
echo
echo "---- utils/formatCode.js (full) ----"
sed -n '1,240p' packages/react-generator/src/utils/formatCode.js || true
echo
echo "---- plugins/formatCodePlugin.js (full) ----"
sed -n '1,240p' packages/react-generator/src/plugins/formatCodePlugin.js || true
echo
echo "---- search: unwrapExpression occurrences ----"
rg -n "unwrapExpression" -g "packages/react-generator/**" || true
echo
echo "---- search: literal t('${item.key}') pattern ----"
rg -n "t\\('\\$\\{item\\.key\\}'\\)" -g "packages/react-generator/**" || trueLength of output: 28987
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- parser/state.js (full) ----"
sed -n '1,240p' packages/react-generator/src/parser/state.js || trueLength of output: 1882
Fix quoting/escaping for generated JSX attributes (ensure consistent escaping of single/double quotes)
Short summary: I inspected the generator and formatter. Prettier is configured singleQuote: true / jsxSingleQuote: false (so JSX attributes stay double-quoted), and the generator already handles the "深'i'圳" single-quote case in page.js, but parser/jsx-slot.js emits unescaped double-quoted attributes and is inconsistent. Please unify with a shared escaper.
Files to fix:
- packages/react-generator/src/parser/jsx-slot.js
- Current risky line:
if (typeof item === 'string') return attrsArr.push(\${key}="${item}"`)` - Risk: unescaped double quotes in item will break generated JSX.
- Suggestion: use a shared escaper, e.g.
- Replace with:
if (typeof item === 'string') return attrsArr.push(\${key}="${escapeJsxAttr(item)}"`)`
- Replace with:
- Current risky line:
- packages/react-generator/src/generator/page.js
- Current:
return attrsArr.push(\${key}="${item.replace(/"/g, "'")}"`)` - Suggestion: call the same escaper instead of blanket-replacing double quotes with single quotes to preserve original content:
-
return attrsArr.push(\${key}="${escapeJsxAttr(item)}"`)`
-
- Current:
- New/updated util
- Add a shared util (e.g. packages/react-generator/src/utils/escape.js or export from existing utils):
- export function escapeJsxAttr(s) { return String(s).replace(/\/g, '\\').replace(/"/g, '\"') }
- Use this util in all places that emit raw JSX attribute strings (parser/jsx-slot.js, generator/page.js, others if any).
- Add a shared util (e.g. packages/react-generator/src/utils/escape.js or export from existing utils):
- Tests
- Add a unit test that feeds strings containing single and double quotes (e.g. "深'i'圳" and 'He said "hi"') into generateCode and asserts output is valid JSX (or at least that formatCode + ESLint pass). Put it under packages/react-generator/test/full/ (or add a small unit test).
Why: Prettier's singleQuote setting alone won't prevent broken JSX when generator emits unescaped double-quoted attribute values. Making escaping centralized avoids inconsistent behavior and future regressions.
Suggested one-line diffs (illustrative):
-
packages/react-generator/src/parser/jsx-slot.js
-
- if (typeof item === 'string') return attrsArr.push(
${key}="${item}")
- if (typeof item === 'string') return attrsArr.push(
-
- if (typeof item === 'string') return attrsArr.push(
${key}="${escapeJsxAttr(item)}")
- if (typeof item === 'string') return attrsArr.push(
-
-
packages/react-generator/src/generator/page.js
-
- return attrsArr.push(
${key}="${item.replace(/"/g, "'")}")
- return attrsArr.push(
-
- return attrsArr.push(
${key}="${escapeJsxAttr(item)}")
- return attrsArr.push(
-
| "component": "div", | ||
| "props": { | ||
| "style": "color: rgb(94,124, 224);cursor:pointer;", | ||
| "text": { | ||
| "type": "i18n", | ||
| "key": "operation.delete" | ||
| }, | ||
| "prop1": { | ||
| "a": 123 | ||
| }, | ||
| "visible": true, | ||
| "onClick": { | ||
| "type": "JSExpression", | ||
| "value": "this.emit" | ||
| } | ||
| }, | ||
| "children": [ | ||
| { | ||
| "componentName": "TinyInput", | ||
| "props": { | ||
| "value": { | ||
| "type": "JSExpression", | ||
| "value": "row.giveamount", | ||
| "model": { | ||
| "prop": "" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "component": "span", | ||
| "condition": { | ||
| "type": "JSExpression", | ||
| "value": "state.cityOptions.length" | ||
| }, | ||
| "children": { | ||
| "type": "i18n", | ||
| "key": "operation.hello" | ||
| } | ||
| }, | ||
| { | ||
| "componentName": "Icon", | ||
| "props": { | ||
| "name": "TinyIconHelpCircle", | ||
| "style": "margin-left: 6px; cursor: pointer;vertical-align: top;" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Schema uses both "componentName" and "component" — confirm normalizer supports aliasing
Most nodes here use componentName, but this slot value uses component. Ensure parseSchema/normalization coerces component -> componentName uniformly; otherwise this node may be skipped or misgenerated.
If aliasing isn’t handled, I can add a normalizer in parseSchemaPlugin to map component to componentName.
🏁 Script executed:
#!/bin/bash
# Check if parser handles "component" alias
rg -n -C2 -g "packages/react-generator/**" -P "\\bcomponentName\\b|\\bcomponent\\b"Length of output: 194062
Add normalization for component → componentName to ensure consistent parsing
The pipeline currently handles componentName everywhere (e.g. in parseImport, parseRequiredBlocks) but does not map the alternate component alias into componentName. Although jsx-slot falls back to item.component when generating JSX, downstream stages (imports, block extraction, type checking) rely solely on schema.componentName and will silently skip alias-only entries.
Please add a pre-processor step—ideally in packages/react-generator/src/pre-processor/index.js—immediately before existing transforms to coerce the alias:
src/pre-processor/index.js
// at top, after imports
+const normalizeComponentAlias = (schema) => {
+ // Map legacy `component` field into `componentName` if not already set
+ if (schema.component && !schema.componentName) {
+ schema.componentName = schema.component
+ }
+}
const text2Span = (schema) => {
const { componentName, props = {} } = schema
…
}
// Register alias normalizer before other rules
+export default [
+ normalizeComponentAlias,
+ text2Span,
+ tinyIcon,
+ tinyGrid,
+ collection2Div,
+ component2Block,
+]This guarantees that every node—regardless of using component or componentName—uniformly populates schema.componentName for parsing, import collection, block extraction, and type definitions.
📝 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.
| "component": "div", | |
| "props": { | |
| "style": "color: rgb(94,124, 224);cursor:pointer;", | |
| "text": { | |
| "type": "i18n", | |
| "key": "operation.delete" | |
| }, | |
| "prop1": { | |
| "a": 123 | |
| }, | |
| "visible": true, | |
| "onClick": { | |
| "type": "JSExpression", | |
| "value": "this.emit" | |
| } | |
| }, | |
| "children": [ | |
| { | |
| "componentName": "TinyInput", | |
| "props": { | |
| "value": { | |
| "type": "JSExpression", | |
| "value": "row.giveamount", | |
| "model": { | |
| "prop": "" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "component": "span", | |
| "condition": { | |
| "type": "JSExpression", | |
| "value": "state.cityOptions.length" | |
| }, | |
| "children": { | |
| "type": "i18n", | |
| "key": "operation.hello" | |
| } | |
| }, | |
| { | |
| "componentName": "Icon", | |
| "props": { | |
| "name": "TinyIconHelpCircle", | |
| "style": "margin-left: 6px; cursor: pointer;vertical-align: top;" | |
| } | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } | |
| } | |
| // packages/react-generator/src/pre-processor/index.js | |
| // (existing imports…) | |
| import text2Span from './text2Span' | |
| import tinyIcon from './tinyIcon' | |
| import tinyGrid from './tinyGrid' | |
| import collection2Div from './collection2Div' | |
| import component2Block from './component2Block' | |
| // Normalize legacy `component` field into `componentName` | |
| const normalizeComponentAlias = (schema) => { | |
| // Map legacy `component` field into `componentName` if not already set | |
| if (schema.component && !schema.componentName) { | |
| schema.componentName = schema.component | |
| } | |
| } | |
| export default [ | |
| normalizeComponentAlias, | |
| text2Span, | |
| tinyIcon, | |
| tinyGrid, | |
| collection2Div, | |
| component2Block, | |
| ] |
🤖 Prompt for AI Agents
In packages/react-generator/test/full/case1_normal/input/page.schema.json around
lines 574 to 626, nodes use the alias "component" instead of the
pipeline-standard "componentName", causing downstream steps to miss those nodes;
add a pre-processor in packages/react-generator/src/pre-processor/index.js that
runs immediately before the existing transforms and walks the schema tree
recursively, normalizing each node by setting schema.componentName =
schema.componentName || schema.component (without overwriting an existing
componentName), handling objects and arrays, and preserving other properties so
imports, block extraction, and type generation always see componentName
populated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (11)
packages/react-generator/test/generator/expected/appdemo01/src/views/createVm/createVm.jsx (3)
184-214: Switch Table block to AntD API (remove TinyUI props).Current props (
editConfig,options,auto-resize,field,sortable) are not AntD Table props. Replace with AntD-conformantcolumns,dataSource,rowSelection,pagination.- <Table - editConfig={{ trigger: 'click', mode: 'cell', showStatus: true }} - columns={[ - { type: 'radio', width: 60 }, - { field: 'employees', title: '规格名称' }, - { field: 'created_date', title: 'vCPUs | 内存(GiB)', sortable: true }, - { field: 'city', title: 'CPU', sortable: true }, - { title: '基准 / 最大带宽\t', sortable: true }, - { title: '内网收发包', sortable: true } - ]} - options={[ - { - id: '1', - name: 'GFD科技有限公司', - city: '福州', - employees: 800, - created_date: '2014-04-30 00:56:00', - boole: false - }, - { - id: '2', - name: 'WWW科技有限公司', - city: '深圳', - employees: 300, - created_date: '2016-07-08 12:36:22', - boole: true - } - ]} - style={{ marginTop: '12px', borderRadius: '0px' }} - auto-resize={true} - ></Table> + <Table + rowSelection={{ type: 'radio' }} + columns={[ + { title: '规格名称', dataIndex: 'employees' }, + { title: 'vCPUs | 内存(GiB)', dataIndex: 'created_date', sorter: true }, + { title: 'CPU', dataIndex: 'city', sorter: true }, + { title: '基准 / 最大带宽', dataIndex: 'bandwidth', sorter: true }, + { title: '内网收发包', dataIndex: 'pps', sorter: true } + ]} + dataSource={[ + { key: '1', employees: 800, created_date: '2014-04-30 00:56:00', city: '福州', bandwidth: '-', pps: '-' }, + { key: '2', employees: 300, created_date: '2016-07-08 12:36:22', city: '深圳', bandwidth: '-', pps: '-' } + ]} + pagination={false} + style={{ marginTop: '12px', borderRadius: '0px' }} + />
244-250: Replace non-AntD Form props with valid AntD API.Props like
labelCol="80px",layout={false},label-position,label-widthare invalid in AntD. Uselayout="horizontal"and objectlabelCol/wrapperCol.- <Form - labelCol="80px" - layout={false} - label-position="left " - label-width="150px" - style={{ borderRadius: '0px' }} - > + <Form + layout="horizontal" + labelCol={{ span: 6 }} + wrapperCol={{ span: 18 }} + style={{ borderRadius: '0px' }} + >- <Form - labelCol="80px" - layout={false} - label-position="left " - label-width="150px" - style={{ borderRadius: '0px' }} - > + <Form + layout="horizontal" + labelCol={{ span: 6 }} + wrapperCol={{ span: 18 }} + style={{ borderRadius: '0px' }} + >- <Form - labelCol="80px" - layout={false} - label-position="left " - label-width="150px" - style={{ borderRadius: '0px' }} - > + <Form + layout="horizontal" + labelCol={{ span: 6 }} + wrapperCol={{ span: 18 }} + style={{ borderRadius: '0px' }} + >Also applies to: 306-312, 341-347
349-387: Add stable key to items rendered by map.Missing
keybreaks reconciliation and fails lint.- {state.dataDisk.map((item) => ( - <div style={{ marginTop: '12px', display: 'flex' }}> + {state.dataDisk.map((item) => ( + <div key={item} style={{ marginTop: '12px', display: 'flex' }}>packages/react-generator/test/generator/mockData.js (3)
333-338: Mask token-like test data to avoid secret scanners.Replace
confirmationTokenwith a safe placeholder.- confirmationToken: 'dfb2c162-351f-4f44-ad5f-8998', + confirmationToken: 'PLACEHOLDER_TOKEN',Also applies to: 1429-1434
38-58: Duplicate key “options” overwrites the array; rename the request config.Second
options(request config) clobbers the data array. Rename torequest.], - options: { + request: { uri: '', method: 'GET' },
888-893: Drop duplicate Form “layout”; the latter overwrites the former.Keep a single intended value (e.g.,
'top').- layout: 'top', - layout: false, + layout: 'top',- layout: 'top', - layout: false, + layout: 'top',- layout: 'top', - layout: false, + layout: 'top',Also applies to: 1027-1032, 1102-1107
packages/react-generator/src/generator/page.js (5)
26-26: Fix typo in import pathThe import path has a typo -
uaperCaseshould beupperCase.-import { capitalizeFirstLetter } from '../utils/uaperCase' +import { capitalizeFirstLetter } from '../utils/upperCase'
437-438: Replace require() with ES6 importUse ES6 import syntax instead of CommonJS require for consistency with the rest of the codebase.
Add the import at the top of the file with other imports:
+import path from 'path'Then update the usage:
- const path = require('path') - const relativePath = path?.relative(fromPath, toPath).replace(/\\/g, '/') + const relativePath = path.relative(fromPath, toPath).replace(/\\/g, '/')
97-134: Resolve mutual recursion between functionsThe mutual recursion between
recurseJSXChildrenandgenerateJSXNodecauses an ESLint error. While this works at runtime due to hoisting, it's better to reorganize the code.Consider using a forward declaration pattern:
+// Forward declaration to resolve mutual recursion +let generateJSXNode + // 工具函数:处理JSX子元素 const recurseJSXChildren = (children, state, description, result, isClassComponent = false) => { // ... existing code ... } // ... other helper functions ... // 工具函数:生成JSX节点 -const generateJSXNode = (schema, state, description, isRootNode = false, isClassComponent = false) => { +generateJSXNode = (schema, state, description, isRootNode = false, isClassComponent = false) => { // ... existing code ... }
192-230: Remove unused parametersThe
descriptionandstateparameters are declared but never used in thehandleJSXDataBindingfunction.-const handleJSXDataBinding = ({ key, item, attrsArr, description, state, isClassComponent = false, hasOnChange = false, componentName = '' }) => { +const handleJSXDataBinding = ({ key, item, attrsArr, isClassComponent = false, hasOnChange = false, componentName = '' }) => {Also update the call site on line 245:
- const result = handleJSXDataBinding({key, item, attrsArr, description, state, isClassComponent, hasOnChange, componentName}) + const result = handleJSXDataBinding({key, item, attrsArr, isClassComponent, hasOnChange, componentName})
147-151: Fix event handler to spread arguments correctlyThe current implementation passes
eventArgsas a single array argument instead of spreading it, which may break handlers expecting individual event arguments.if (item.params?.length) { const extendParams = item.params.join(',') - return `${key}={(...eventArgs) => ${eventHandler}(eventArgs, ${extendParams})}` + return `${key}={(...eventArgs) => ${eventHandler}(...eventArgs, ${extendParams})}` } else {
🧹 Nitpick comments (7)
packages/react-generator/test/generator/expected/appdemo01/src/views/createVm/createVm.jsx (2)
79-86: Stop using the children prop; write JSX children.Biome flags
noChildrenProp. Prefer element children for readability and consistency.- <Typography.Text - children="温馨提示:页面左上角切换区域" - style={{ color: '#8a8e99', fontSize: '12px' }} - ></Typography.Text> + <Typography.Text style={{ color: '#8a8e99', fontSize: '12px' }}> + 温馨提示:页面左上角切换区域 + </Typography.Text> - <Typography.Text - children="不同区域的云服务产品之间内网互不相通;请就近选择靠近您业务的区域,可减少网络时延,提高访问速度" - style={{ display: 'block', color: '#8a8e99', borderRadius: '0px', fontSize: '12px' }} - ></Typography.Text> + <Typography.Text style={{ display: 'block', color: '#8a8e99', borderRadius: '0px', fontSize: '12px' }}> + 不同区域的云服务产品之间内网互不相通;请就近选择靠近您业务的区域,可减少网络时延,提高访问速度 + </Typography.Text>- <Typography.Text - children="当前规格" - style={{ width: '150px', display: 'inline-block' }} - ></Typography.Text> + <Typography.Text style={{ width: '150px', display: 'inline-block' }}> + 当前规格 + </Typography.Text> - <Typography.Text - children="通用计算型 | Si2.large.2 | 2vCPUs | 4 GiB" - style={{ fontWeight: '700' }} - ></Typography.Text> + <Typography.Text style={{ fontWeight: '700' }}> + 通用计算型 | Si2.large.2 | 2vCPUs | 4 GiB + </Typography.Text>- <Typography.Text children="购买量" style={{ marginRight: '10px' }}></Typography.Text> + <Typography.Text style={{ marginRight: '10px' }}>购买量</Typography.Text> @@ - <Typography.Text children="台"></Typography.Text> + <Typography.Text>台</Typography.Text> @@ - <Typography.Text children="配置费用" style={{ fontSize: '12px' }}></Typography.Text> + <Typography.Text style={{ fontSize: '12px' }}>配置费用</Typography.Text> - <Typography.Text - children="¥1.5776" - style={{ paddingLeft: '10px', color: '#de504e' }} - ></Typography.Text> + <Typography.Text style={{ paddingLeft: '10px', color: '#de504e' }}> + ¥1.5776 + </Typography.Text> - <Typography.Text children="/小时" style={{ fontSize: '12px' }}></Typography.Text> + <Typography.Text style={{ fontSize: '12px' }}>/小时</Typography.Text> @@ - <Typography.Text - children="参考价格,具体扣费请以账单为准。" - style={{ fontSize: '12px', borderRadius: '0px' }} - ></Typography.Text> + <Typography.Text style={{ fontSize: '12px', borderRadius: '0px' }}> + 参考价格,具体扣费请以账单为准。 + </Typography.Text> - <Typography.Text - children="了解计费详情" - style={{ fontSize: '12px', color: '#344899' }} - ></Typography.Text> + <Typography.Text style={{ fontSize: '12px', color: '#344899' }}> + 了解计费详情 + </Typography.Text>Also applies to: 216-223, 423-452
420-471: Use numeric spans for AntD Grid.
spanshould be a number, not a string.- <Col span="16"> + <Col span={16}> @@ - <Col span="6"> + <Col span={6}> @@ - <Col span="7"> + <Col span={7}> @@ - <Col - span="8" + <Col + span={8}packages/react-generator/test/generator/mockData.js (2)
83-86: Typo in function name inside data handler string.
dataHanlder→dataHandler(cosmetic but reduces confusion).- value: 'function dataHanlder(res){\n return res;\n}' + value: 'function dataHandler(res){\n return res;\n}'
773-829: Align Table schema with target AntD mapping (generator responsibility).DSL uses TinyUI-like props (
editConfig,options,field,sortable,'auto-resize'). Ensure the React generator plugin normalizes these to AntD (columns[{ title, dataIndex, sorter }],dataSource,rowSelection,pagination). If keeping DSL as-is, the generator should transform at emit time to avoid leaking non-AntD props into output.If desired, I can patch the generator’s Table emitter to perform this normalization and update the tests accordingly. Confirm the intended target (AntD) and I’ll send a focused PR diff.
packages/react-generator/src/generator/page.js (3)
338-340: Expand void elements list for completenessThe current list of void elements is incomplete. Consider adding other HTML5 void elements to ensure proper self-closing tag handling.
- const VOID_ELEMENTS = ['img', 'input', 'br', 'hr', 'link'] + const VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']
525-534: Improve setState conversion for function componentsThe regex-based conversion from class-style
this.setStateto hooks may miss complex cases or nested setState calls.Consider a more robust conversion approach that handles nested objects and function-based setState:
const convertedBody = body - .replace(/this\.setState\(\s*\{\s*(\w+):\s*([^}]+)\s*\}\s*\)/g, (match, stateName, value) => { - const setterName = `set${stateName.charAt(0).toUpperCase()}${stateName.slice(1)}` - return `${setterName}(${value})` - }) + .replace(/this\.setState\(\s*\{\s*(\w+):\s*([^}]+)\s*\}\s*\)/g, (match, stateName, value) => { + return `setState(prev => ({ ...prev, ${stateName}: ${value} }))` + }) + .replace(/this\.setState\(\s*prev\s*=>\s*\(([\s\S]+?)\)\s*\)/g, (match, stateUpdate) => { + return `setState(prev => (${stateUpdate}))` + })
569-585: Consider lifecycle method compatibilityThe mapping of lifecycle methods to hooks looks correct, but note that
shouldComponentUpdatedoesn't have a direct hooks equivalent. UsinguseMemomay not provide the same optimization behavior.Would you like me to suggest a more appropriate hooks pattern for
shouldComponentUpdateoptimization, perhaps usingReact.memowith a custom comparison function?
📜 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 (3)
packages/react-generator/src/generator/page.js(1 hunks)packages/react-generator/test/generator/expected/appdemo01/src/views/createVm/createVm.jsx(1 hunks)packages/react-generator/test/generator/mockData.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/react-generator/src/generator/page.js (7)
packages/react-generator/src/templates/react-template/index.js (1)
value(30-30)packages/react-generator/src/utils/index.js (15)
description(128-128)result(26-26)result(48-48)result(56-56)iconName(146-146)handleIconInProps(145-152)avoidDuplicateString(55-65)isOn(92-92)toPascalCase(75-75)exportName(131-131)addIconRecord(127-136)addAccessorRecord(109-117)getFunctionInfo(24-39)getTypeOfSchema(22-22)prettierOpts(83-87)packages/react-generator/src/constant/index.js (6)
BUILTIN_COMPONENT_NAME(385-393)IntrinsicElements(408-587)AntdComponents(291-380)TINY_ICON(398-398)DEFAULT_COMPONENTS_MAP(17-264)BUILTIN_COMPONENTS_MAP(267-289)packages/react-generator/src/pre-processor/index.js (8)
iconName(35-35)schema(16-16)schema(31-31)schema(44-44)schema(54-54)schema(64-64)schema(79-79)preProcess(78-92)packages/react-generator/src/parser/state.js (2)
unwrapExpression(34-37)translateHookState(43-57)packages/react-generator/src/utils/uaperCase.js (2)
capitalizeFirstLetter(1-4)capitalizeFirstLetter(1-4)packages/react-generator/src/utils/formatCode.js (1)
formatCode(113-136)
packages/react-generator/test/generator/expected/appdemo01/src/views/createVm/createVm.jsx (1)
packages/react-generator/test/generator/expected/appdemo01/src/router/index.jsx (1)
CreateVm(6-6)
🪛 Biome (2.1.2)
packages/react-generator/test/generator/mockData.js
[error] 38-55: This property is later overwritten by an object member with the same name.
Overwritten with this property.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.
(lint/suspicious/noDuplicateObjectKeys)
[error] 905-905: This property is later overwritten by an object member with the same name.
Overwritten with this property.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.
(lint/suspicious/noDuplicateObjectKeys)
[error] 1046-1047: This property is later overwritten by an object member with the same name.
Overwritten with this property.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.
(lint/suspicious/noDuplicateObjectKeys)
[error] 1122-1122: This property is later overwritten by an object member with the same name.
Overwritten with this property.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.
(lint/suspicious/noDuplicateObjectKeys)
packages/react-generator/test/generator/expected/appdemo01/src/views/createVm/createVm.jsx
[error] 80-80: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 84-84: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 136-137: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 148-148: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 162-162: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 228-229: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 235-235: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 360-360: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
[error] 436-436: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 446-446: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 449-450: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 452-453: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 460-460: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 464-464: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 471-471: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
🪛 ESLint
packages/react-generator/src/generator/page.js
[error] 110-110: 'generateJSXNode' was used before it was defined.
(@typescript-eslint/no-use-before-define)
[error] 192-192: 'description' is defined but never used. Allowed unused args must match /^_/u.
(@typescript-eslint/no-unused-vars)
[error] 192-192: 'state' is defined but never used. Allowed unused args must match /^_/u.
(@typescript-eslint/no-unused-vars)
[error] 437-437: A require() style import is forbidden.
(@typescript-eslint/no-require-imports)
🔇 Additional comments (2)
packages/react-generator/src/generator/page.js (2)
57-94: LGTM! Well-structured state update logicThe
generateUpdateLogicfunction properly handles both simple and nested state updates, with good support for different component types (checkbox vs regular inputs) and both class and functional components.
650-657: Add useTranslation hook conditionallyGood implementation of conditional import for the i18n hook based on actual usage in the JSX.
| const cmp = AntdComponents.filter((item) => item.subName).find((item) => item.componentName === component) | ||
| if (cmp) { | ||
| // 记录主组件使用 | ||
| description.componentSet.add(cmp.exportName) | ||
| // 在JSX中使用正确的组件名称 | ||
| component = `${cmp.exportName}.${cmp.subName}` | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify Ant Design subcomponent handling
Please verify that the Ant Design subcomponent handling correctly manages components like Radio.Group, Select.Option, etc.
🏁 Script executed:
#!/bin/bash
# Check if AntdComponents array properly defines subcomponents
rg -A 5 "subName.*Group|subName.*Option" packages/react-generator/src/constant/index.js
# Verify the component mapping structure
ast-grep --pattern 'const AntdComponents = [$$$]' packages/react-generator/src/constant/index.js | head -20Length of output: 1859
Add missing Select.Option and Radio.Group mappings
In packages/react-generator/src/constant/index.js, the AntdComponents array currently only defines subcomponents with subName: 'Group' for Checkbox and Input. Add entries for:
• Select.Option
• Radio.Group
Example additions:
{
componentName: 'AntdSelectOption',
destructuring: true,
exportName: 'Select',
subName: 'Option',
package: 'antd',
version: '^5.16.0'
},
{
componentName: 'AntdRadioGroup',
destructuring: true,
exportName: 'Radio',
subName: 'Group',
package: 'antd',
version: '^5.16.0'
},🤖 Prompt for AI Agents
In packages/react-generator/src/generator/page.js around lines 286 to 292, the
code maps Antd subcomponents but the AntdComponents constant lacks entries for
Select.Option and Radio.Group; update
packages/react-generator/src/constant/index.js to add two entries: one mapping
AntdSelectOption to destructured exportName "Select" with subName "Option" and
package/version as the other antd entries, and one mapping AntdRadioGroup to
destructured exportName "Radio" with subName "Group" and matching
package/version; ensure these entries follow the same shape/order as existing
objects so the generator will detect cmp and set component to "Select.Option"
and "Radio.Group" respectively.
| <Typography.Text label="请注意操作系统的语言类型。" style={{ color: '#e37d29' }}></Typography.Text> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix invalid Typography.Text prop “label”; use children instead.
Typography.Text doesn’t support label; content isn’t rendered.
- <div style={{ marginTop: '12px' }}>
- <Typography.Text label="请注意操作系统的语言类型。" style={{ color: '#e37d29' }}></Typography.Text>
- </div>
+ <div style={{ marginTop: '12px' }}>
+ <Typography.Text style={{ color: '#e37d29' }}>
+ 请注意操作系统的语言类型。
+ </Typography.Text>
+ </div>- <Typography.Text
- label="GiB
-IOPS上限240,IOPS突发上限5,000"
- style={{ color: '#575d6c', fontSize: '12px' }}
- ></Typography.Text>
+ <Typography.Text style={{ color: '#575d6c', fontSize: '12px' }}>
+ GiB IOPS上限240,IOPS突发上限5,000
+ </Typography.Text>- <Typography.Text
- label="GiB
-IOPS上限600,IOPS突发上限5,000"
- style={{ color: '#575d6c', fontSize: '12px', marginRight: '10px' }}
- ></Typography.Text>
+ <Typography.Text style={{ color: '#575d6c', fontSize: '12px', marginRight: '10px' }}>
+ GiB IOPS上限600,IOPS突发上限5,000
+ </Typography.Text>- <Typography.Text
- label="增加一块数据盘"
- style={{ fontSize: '12px', borderRadius: '0px', marginRight: '10px' }}
- ></Typography.Text>
+ <Typography.Text style={{ fontSize: '12px', borderRadius: '0px', marginRight: '10px' }}>
+ 增加一块数据盘
+ </Typography.Text>
- <Typography.Text
- label="您还可以挂载 21 块磁盘(云硬盘)"
- style={{ color: '#8a8e99', fontSize: '12px' }}
- ></Typography.Text>
+ <Typography.Text style={{ color: '#8a8e99', fontSize: '12px' }}>
+ 您还可以挂载 21 块磁盘(云硬盘)
+ </Typography.Text>Also applies to: 333-337, 374-377, 391-397
🤖 Prompt for AI Agents
In
packages/react-generator/test/generator/expected/appdemo01/src/views/createVm/createVm.jsx
around lines 286-287 (also update occurrences at 333-337, 374-377, 391-397): the
JSX uses a non-existent Typography.Text prop "label" so text isn't rendered;
replace the label prop by moving its string into the component children (e.g.
<Typography.Text style={{...}}>请注意操作系统的语言类型。</Typography.Text>) and remove the
label attribute, ensuring all four listed locations are updated the same way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (7)
packages/react-generator/README.md (7)
3-3: Fix badge: use npm version badge instead of pnpm to ensure it renders correctly on Shields.io.Apply:
-[](https://www.npmjs.com/package/@opentiny/tiny-engine-dsl-react) +[](https://www.npmjs.com/package/@opentiny/tiny-engine-dsl-react)
90-110: State update for deep paths is correct; consider guiding toward useReducer for complex nested state.
Optional doc tip to avoid repetitive spread code for deeply nested structures.
165-181: Separate Prettier options from plugin options in the snippet for clarity.
Minor doc tweak: group “enableFormat/skipFiles/onlyFiles/logFormatResult” under a nested plugin key to avoid confusing them with Prettier core options.
304-324: Add language to fenced code block (markdownlint MD040).-``` +```text src/ ├── generator/ # 代码生成器核心 │ ├── codeGenerator.js │ ├── generateApp.js │ ├── generateJsx.js │ ├── page.js │ └── parseImport.js ├── plugins/ # 插件系统 │ ├── formatCodePlugin.js │ ├── genBlockPlugin.js │ ├── genPagePlugin.js │ └── ... ├── templates/ # 代码模板 │ └── react-template/ ├── parser/ # DSL解析器 ├── pre-processor/ # 预处理器 ├── utils/ # 工具函数 └── constant/ # 常量定义--- `329-334`: **Add language to ASCII diagram code block (markdownlint MD040).** ```diff -``` +```text ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Plugin │ │ Template │ │ Parser │ │ Generator │ │ 插件系统 │───▶│ 模板系统 │───▶│ 解析器 │───▶│ 代码生成器 │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘--- `6-18`: **Add a brief English summary or links for non-Chinese readers.** Optional, but improves discoverability for the public package. --- `19-23`: **Add usage note about package status and changelog.** Since this is a new public package, add a line linking to CHANGELOG for any future breaking changes (per project convention). I used the team learning that breaking changes are documented in the changelog for this repository. </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **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. <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between f3d307ffc84174c13af4f5c6ce0d27e928a7cf44 and d9da61281466ea79f98d4a31513c6b4a144391a9. </details> <details> <summary>📒 Files selected for processing (1)</summary> * `packages/react-generator/README.md` (1 hunks) </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧠 Learnings (1)</summary> <details> <summary>📚 Learning: 2025-01-14T08:42:18.574Z</summary>Learnt from: gene9831
PR: #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/react-generator/README.md` </details> </details><details> <summary>🪛 markdownlint-cli2 (0.17.2)</summary> <details> <summary>packages/react-generator/README.md</summary> 304-304: Fenced code blocks should have a language specified (MD040, fenced-code-language) --- 329-329: Fenced code blocks should have a language specified (MD040, fenced-code-language) </details> </details> </details> <details> <summary>🔇 Additional comments (5)</summary><blockquote> <details> <summary>packages/react-generator/README.md (5)</summary><blockquote> `67-86`: **Clarify event shape in data-binding example.** TinyInput.onChange may emit value (val) vs event (e). Document which one is expected to prevent incorrect handlers. --- `127-156`: **Docs show class components; confirm generator output style.** If the generator primarily emits function components with hooks, align the example; otherwise, state that lifecycle mapping uses class components (e.g., for componentDidCatch). --- `202-213`: **Align built-in plugin names with actual files/exports.** The list includes parseSchema, router, etc. Ensure names match the implemented plugin identifiers and filenames to prevent config drift. --- `232-252`: **List all public methods/exports exactly as shipped.** Verify that generate, generatePage, and generateBlock are the methods exposed by generateApp; document any additional exports (if any) to avoid API ambiguity. --- `281-292`: **Test scripts validated.** `test:latest` is defined in package.json and all referenced paths (`test/data-binding`, `test/lifecycle`, `test/formatCode`) exist under packages/react-generator. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| - `componentDidMount` - 组件挂载后 | ||
| - `componentWillUnmount` - 组件卸载前 | ||
| - `componentDidUpdate` - 组件更新后 | ||
| - `componentDidCatch` - 错误边界捕获 | ||
| - `componentWillMount` - 组件挂载前 | ||
| - `shouldComponentUpdate` - 控制组件更新 | ||
|
|
||
| #### 使用示例 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid documenting deprecated lifecycle; clarify support.
- componentWillMount is long-deprecated; either drop it or mark as UNSAFE and discourage use.
#### 支持的生命周期
- `componentDidMount` - 组件挂载后
- `componentWillUnmount` - 组件卸载前
- `componentDidUpdate` - 组件更新后
- `componentDidCatch` - 错误边界捕获
-- `componentWillMount` - 组件挂载前
- `shouldComponentUpdate` - 控制组件更新
+> 注:不建议使用已弃用的 `componentWillMount/UNSAFE_componentWillMount`,推荐使用 `useEffect(..., [])` 或初始化逻辑放置于构造/初始化阶段。📝 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.
| - `componentDidMount` - 组件挂载后 | |
| - `componentWillUnmount` - 组件卸载前 | |
| - `componentDidUpdate` - 组件更新后 | |
| - `componentDidCatch` - 错误边界捕获 | |
| - `componentWillMount` - 组件挂载前 | |
| - `shouldComponentUpdate` - 控制组件更新 | |
| #### 使用示例 | |
| - `componentDidMount` - 组件挂载后 | |
| - `componentWillUnmount` - 组件卸载前 | |
| - `componentDidUpdate` - 组件更新后 | |
| - `componentDidCatch` - 错误边界捕获 | |
| - `shouldComponentUpdate` - 控制组件更新 | |
| > 注:不建议使用已弃用的 `componentWillMount/UNSAFE_componentWillMount`,推荐使用 `useEffect(..., [])` 或初始化逻辑放置于构造/初始化阶段。 | |
| #### 使用示例 |
🤖 Prompt for AI Agents
In packages/react-generator/README.md around lines 118 to 126, the lifecycle
list includes the deprecated componentWillMount without qualification; remove it
or mark it as deprecated (UNSAFE_componentWillMount) and add a short note
discouraging its use and recommending alternatives (constructor,
componentDidMount, or hooks like useEffect) so readers aren't encouraged to use
obsolete APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/react-generator/README.md (1)
115-121: Remove deprecated lifecycle from docs; add guidance.
componentWillMountis long-deprecated; documenting it without caveats encourages obsolete patterns. Remove it and add a short note discouraging use, with hook-based alternatives.#### 支持的生命周期 - `componentDidMount` - 组件挂载后 - `componentWillUnmount` - 组件卸载前 - `componentDidUpdate` - 组件更新后 - `componentDidCatch` - 错误边界捕获 -- `componentWillMount` - 组件挂载前 - `shouldComponentUpdate` - 控制组件更新 + +> 注:不建议使用已弃用的 `componentWillMount/UNSAFE_componentWillMount`。推荐使用 `useEffect(() => { /* init */ }, [])`, +> 或将初始化逻辑放置于构造/初始化阶段(函数组件的初始化或类组件构造函数)。
🧹 Nitpick comments (6)
packages/react-generator/README.md (6)
298-317: Fix markdownlint MD040: add language to fenced code block.Label the project-structure block as plain text to satisfy linters and improve rendering.
-``` +```text src/ ├── generator/ # 代码生成器核心 │ ├── codeGenerator.js │ ├── generateApp.js │ ├── generateJsx.js │ ├── page.js │ └── parseImport.js ├── plugins/ # 插件系统 │ ├── formatCodePlugin.js │ ├── genBlockPlugin.js │ ├── genPagePlugin.js │ └── ... ├── templates/ # 代码模板 │ └── react-template/ ├── parser/ # DSL解析器 ├── pre-processor/ # 预处理器 ├── utils/ # 工具函数 └── constant/ # 常量定义--- `323-328`: **Fix markdownlint MD040: add language to ASCII diagram code block.** Mark as text for consistent formatting. ```diff -``` +```text ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Plugin │ │ Template │ │ Parser │ │ Generator │ │ 插件系统 │───▶│ 模板系统 │───▶│ 解析器 │───▶│ 代码生成器 │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘--- `7-9`: **Clarify “双向绑定” phrasing to avoid React semantics confusion.** In React it’s controlled components (one-way data flow) producing a two-way UX. Slight wording tweak prevents misunderstandings. ```diff -- 🔄 **数据双向绑定** - 支持JSDataBinding和JSExpression的双向数据绑定 +- 🔄 **数据绑定(受控组件)** - 通过受控组件模式支持 JSDataBinding 与带 `model` 语义的 JSExpression,实现双向交互效果
139-153: Prefer hook-based lifecycle example to match modern React.You show class lifecycles here but earlier sections use hooks/state. Add a hooks equivalence example so readers adopt current patterns.
class MyComponent extends React.Component { @@ } + +// Hooks 等价写法(推荐) +function MyComponent() { + React.useEffect(() => { + console.log('Component mounted') + return () => console.log('Component unmounted') + }, []) + return <div>Hello World</div> +}
26-35: Document return type/shape ofgenerator.generate(schema).Examples log/assign the result but don’t state whether it’s a文件映射、字符串、或包含入口信息的对象。 Please specify the exact return contract (and TypeScript类型), plus a tiny sample of the structure.
Would you confirm the public shape (e.g.,
{ files: Record<string,string>, entry: string }or similar) and align README withsrc/index.d.ts?Also applies to: 52-54
214-226: Clarify plugin lifecycle and registration points.The example shows a
transformplugin. Document all supported stages (transformStart/transform/transformEnd), execution order, and how multiple plugins compose/are ordered to reduce ambiguity.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/react-generator/README.md(1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
packages/react-generator/README.md
298-298: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
323-323: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (1)
packages/react-generator/README.md (1)
299-317: Verified: README paths exist and exports match.All listed files are present under packages/react-generator/src/generator and exports are confirmed: default CodeGenerator in codeGenerator.js; generateApp exported from generateApp.js (and re-exported in generator/index.js); generateReactFile default-exported in generateJsx.js; parseImport (and genCompImport/getImportMap) exported in parseImport.js. No further changes required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/react-generator/README.md (1)
172-184: Remove/flag deprecated lifecycle; add guidance.componentWillMount is deprecated/UNSAFE. Remove from “支持的生命周期” and add a cautionary note.
#### 支持的生命周期 - `componentDidMount` - 组件挂载后 - `componentWillUnmount` - 组件卸载前 - `componentDidUpdate` - 组件更新后 - `componentDidCatch` - 错误边界捕获 -- `componentWillMount` - 组件挂载前 - `shouldComponentUpdate` - 控制组件更新 + +> 注:不建议使用已弃用的 `componentWillMount/UNSAFE_componentWillMount`。请将初始化逻辑置于构造/初始化阶段,或使用 `useEffect(() => {...}, [])`。
🧹 Nitpick comments (8)
packages/react-generator/README.md (8)
20-25: Fix MD040: add language to fenced block; optional Mermaid upgrade.Add a language to satisfy markdownlint; consider Mermaid for readability.
-``` +```txt ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Plugin │ │ Template │ │ Parser │ │ Generator │ │ 插件系统 │───▶│ 模板系统 │───▶│ 解析器 │───▶│ 代码生成器 │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘Optional: ```mermaid flowchart LR A[Plugin 插件系统] --> B[Template 模板系统] B --> C[Parser 解析器] C --> D[Generator 代码生成器]
29-48: Fix MD040 on project-structure block.Specify a language (use txt).
-``` +```txt src/ ├── generator/ # 代码生成器核心 ...--- `127-146`: **Split DSL and React snippets; use correct languages (json/jsx).** Current single javascript block mixes JSON DSL and React code. Split and label for clarity and linting. ```diff -```javascript -// DSL Schema -{ +```json +{ "componentName": "TinyInput", "props": { "value": { "type": "JSDataBinding", "value": "this.state.username" } } } - -// 生成的React代码 -const [state, setState] = useState({ username: '' }) - -<TinyInput - value={state.username} - onChange={e => setState(prev => ({ ...prev, username: e.target.value }))} -/> +``` + +```jsx +const [state, setState] = useState({ username: '' }) + +<TinyInput + value={state.username} + onChange={e => setState(prev => ({ ...prev, username: e.target.value }))} +/>--- `151-170`: **Do the same split for the nested-binding example.** Label DSL as json and generated code as jsx. ```diff -```javascript -// DSL Schema -{ +```json +{ "componentName": "TinyInput", "props": { "value": { "type": "JSDataBinding", "value": "this.state.userInfo.name" } } } - -// 生成的React代码 -<TinyInput - value={state.userInfo.name} - onChange={e => setState(prev => ({ - ...prev, - userInfo: { ...prev.userInfo, name: e.target.value } - }))} -/> +``` + +```jsx +<TinyInput + value={state.userInfo.name} + onChange={e => setState(prev => ({ + ...prev, + userInfo: { ...prev.userInfo, name: e.target.value } + }))} +/>--- `225-242`: **Separate Prettier options from plugin options to avoid confusion.** Document which keys are forwarded to Prettier vs. plugin-only. Suggest a nested `prettierOptions`. ```diff const generator = generateApp({ pluginConfig: { formatCode: { - // Prettier 配置 - printWidth: 100, - tabWidth: 2, - semi: true, - singleQuote: true, - - // 插件配置 - enableFormat: true, - skipFiles: ['txt', 'log'], - onlyFiles: ['js', 'jsx', 'ts', 'tsx'], - logFormatResult: true + enableFormat: true, + skipFiles: ['txt', 'log'], + onlyFiles: ['js', 'jsx', 'ts', 'tsx'], + logFormatResult: true, + // Prettier 配置(仅此对象会传递给 Prettier) + prettierOptions: { + printWidth: 100, + tabWidth: 2, + semi: true, + singleQuote: true + } } } })
50-56: Add environment prerequisites.Document required Node/PNPM versions and workspace usage to reduce setup friction.
## 开发环境 + +建议环境 +- Node.js >= 18 +- pnpm >= 8 +- 在 monorepo 中通过 `pnpm -F @opentiny/tiny-engine-dsl-react` 进行安装/测试
294-312: Document generate() output shape.Specify what
generate(schema)returns (文件树结构?字符串集合?写入磁盘与否?) 以便集成方对接。Example addition:
- 返回值
files: 生成文件的 Map<{path:string, content:string}>entry: 应用入口文件路径warnings: string[]write(outDir?: string): 将 files 落盘的便捷方法
285-289: Align customPlugins keys with hook phases.Earlier示例与 CodeGenerator 钩子
transformStart/transform/transformEnd不完全对应。建议在自定义插件示例中对齐并展示全部可用阶段。const generator = generateApp({ customPlugins: { - transform: [customPlugin] + transformStart: [], + transform: [customPlugin], + transformEnd: [] } })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/react-generator/README.md(1 hunks)packages/react-generator/src/templates/react-template/template-files/packageJson.js(1 hunks)packages/react-generator/test/generator/expected/appdemo01/package.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/react-generator/test/generator/expected/appdemo01/package.json
- packages/react-generator/src/templates/react-template/template-files/packageJson.js
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
packages/react-generator/README.md
20-20: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
29-29: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (2)
packages/react-generator/README.md (2)
8-9: Use React‑accurate terminology (avoid “双向绑定”).React is one‑way data flow with controlled components. Reword to prevent confusion.
[ suggest_nitpick_refactor ]
- - 🔄 **数据双向绑定** - 支持JSDataBinding和JSExpression的双向数据绑定 + - 🔄 **受控数据绑定** - 支持 JSDataBinding 与具备 model 语义的 JSExpression(基于受控组件与单向数据流)
187-216: Clarify emitted component style (class vs function).Examples mix hooks and class components. State whether the生成器输出为函数组件、类组件,或按 DSL 决定,并注明对
componentDidCatch需 Error Boundary(函数组件无等价生命周期)。
English | 简体中文
PR Checklist
PR Type
Background and solution
What is the current behavior?
What is the new behavior?
This PR introduces a new React generator package and its tests:
@opentiny/tiny-engine-dsl-react(packages/react-generator)How to validate locally:
Does this PR introduce a breaking change?
Summary by CodeRabbit
New Features
Documentation
Tests
Chores