From b609ab7f4093bb27827ba42c3e5fbdfd571e4993 Mon Sep 17 00:00:00 2001 From: koharxnp Date: Thu, 20 Aug 2026 14:58:21 +0900 Subject: [PATCH 1/4] fix(vite-plugin-gea): ignore comments when checking store fallback triggers --- .../transform/transform-store.ts | 3 +- .../closure-codegen/transform-store.test.ts | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts diff --git a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts index 24abd01..02f6106 100644 --- a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts +++ b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts @@ -59,7 +59,8 @@ export function transformCompiledStoreModule( ir: fallbackIrs[0], irs: fallbackIrs, } - if (/\b(flushSync|silent|Store\.|new\s+Store\s*\()/.test(source)) { + const codeWithoutComments = source.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '') + if (/\b(flushSync|silent|Store\.|new\s+Store\s*\()/.test(codeWithoutComments)) { return fallback } // All-or-nothing across the module's store classes: a partial transform diff --git a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts new file mode 100644 index 0000000..5aedf22 --- /dev/null +++ b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { transformCompiledStoreModule } from '../../src/closure-codegen/transform/transform-store.ts' + +describe('transformCompiledStoreModule', () => { + it('should transform to CompiledStore without false positives from JSDoc comments containing Store.', () => { + const input = ` + import { Store } from '@geajs/core' + /** + * JSDoc comment test: Store.property + * This is Store. + * .Store + */ + export class MyStore extends Store { + data = [] + selected = null + run() {} + runLots() {} + add() {} + update() {} + clear() {} + swapRows() {} + select() {} + remove() {} + } + export default new MyStore() + ` + + const result = transformCompiledStoreModule(input, 'MyStore.ts') + assert.ok(result) + assert.strictEqual(result.changed, true) + assert.match(result.code, /Compiled/) + }) + + it('should fall back when source code contains static Store. calls', () => { + const input = ` + import { Store } from '@geajs/core' + Store.someMethod() + export class MyStore extends Store {} + export default new MyStore() + ` + + const result = transformCompiledStoreModule(input, 'MyStore.ts') + assert.ok(result) + assert.strictEqual(result.changed, false) + assert.doesNotMatch(result.code, /CompiledStore/) + }) +}) \ No newline at end of file From 775a34f0de9401797152e5de6d0ef1296bf77ec0 Mon Sep 17 00:00:00 2001 From: koharxnp Date: Thu, 20 Aug 2026 15:23:29 +0900 Subject: [PATCH 2/4] fix(vite-plugin-gea): preserve string literals when stripping comments for store fallback checks --- .../closure-codegen/transform/transform-store.ts | 9 ++++++++- .../tests/closure-codegen/transform-store.test.ts | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts index 02f6106..dd0c6a5 100644 --- a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts +++ b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts @@ -20,6 +20,13 @@ export interface StoreTransformResult { export type ResolveImportPath = (importer: string, source: string) => string | null +function stripComments(code: string): string { + return code.replace( + /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\/\*[\s\S]*?\*\/|\/\/.*)/g, + (match, stringGroup) => (stringGroup ? stringGroup : '') + ) +} + export function transformCompiledStoreModule( source: string, moduleId = '', @@ -59,7 +66,7 @@ export function transformCompiledStoreModule( ir: fallbackIrs[0], irs: fallbackIrs, } - const codeWithoutComments = source.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '') + const codeWithoutComments = stripComments(source) if (/\b(flushSync|silent|Store\.|new\s+Store\s*\()/.test(codeWithoutComments)) { return fallback } diff --git a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts index 5aedf22..da03dd8 100644 --- a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts +++ b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts @@ -45,4 +45,19 @@ describe('transformCompiledStoreModule', () => { assert.strictEqual(result.changed, false) assert.doesNotMatch(result.code, /CompiledStore/) }) + + it('should fall back when Store. is called on the same line after // in string literal', () => { + const input = ` + import { Store } from '@geajs/core' + const url = "https://example.com"; Store.someMethod() + export class MyStore extends Store { + data = [] + run() {} + } + export default new MyStore() + ` + const result = transformCompiledStoreModule(input, 'MyStore.ts') + assert.ok(result) + assert.strictEqual(result.changed, false) + }) }) \ No newline at end of file From 614093af2cbbdcca70e8878e46f66d1f5260ff8e Mon Sep 17 00:00:00 2001 From: koharxnp Date: Fri, 21 Aug 2026 09:07:03 +0900 Subject: [PATCH 3/4] refactor(vite-plugin-gea): replace regex with AST traversal for store fallback detection --- .../transform/transform-store.ts | 50 ++++++++++++++++--- .../closure-codegen/transform-store.test.ts | 32 ++++++++++-- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts index dd0c6a5..1b8d742 100644 --- a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts +++ b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts @@ -20,11 +20,48 @@ export interface StoreTransformResult { export type ResolveImportPath = (importer: string, source: string) => string | null -function stripComments(code: string): string { - return code.replace( - /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\/\*[\s\S]*?\*\/|\/\/.*)/g, - (match, stringGroup) => (stringGroup ? stringGroup : '') - ) +function hasUnsafeStorePattern(ast: File, storeLocalName: string): boolean { + let unsafe = false + + function walk(node: any) { + if (!node || typeof node !== 'object' || unsafe) return + + if (Array.isArray(node)) { + for (const child of node) walk(child) + return + } + + if (t.isIdentifier(node) && (node.name === 'flushSync' || node.name === 'silent')) { + unsafe = true + return + } + + if ( + t.isMemberExpression(node) && + t.isIdentifier(node.object, { name: storeLocalName }) + ) { + unsafe = true + return + } + + if ( + t.isNewExpression(node) && + t.isIdentifier(node.callee, { name: storeLocalName }) + ) { + unsafe = true + return + } + + for (const key of Object.keys(node)) { + if (key === 'loc' || key === 'start' || key === 'end' || key === 'type' || key === 'comments' || key === 'leadingComments' || key === 'trailingComments') { + continue + } + walk(node[key]) + } + } + + walk(ast.program) + return unsafe } export function transformCompiledStoreModule( @@ -66,8 +103,7 @@ export function transformCompiledStoreModule( ir: fallbackIrs[0], irs: fallbackIrs, } - const codeWithoutComments = stripComments(source) - if (/\b(flushSync|silent|Store\.|new\s+Store\s*\()/.test(codeWithoutComments)) { + if (hasUnsafeStorePattern(ast, imported.localName)) { return fallback } // All-or-nothing across the module's store classes: a partial transform diff --git a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts index da03dd8..59ec9e0 100644 --- a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts +++ b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts @@ -29,7 +29,32 @@ describe('transformCompiledStoreModule', () => { const result = transformCompiledStoreModule(input, 'MyStore.ts') assert.ok(result) assert.strictEqual(result.changed, true) - assert.match(result.code, /Compiled/) + assert.match(result.code, /extends Compiled(?:Lean)?Store/) + }) + + it('should transform without false positives from single-line comments containing Store.', () => { + const input = ` + import { Store } from '@geajs/core' + // FIXME: Store.reset() needs fix + export class MyStore extends Store { + data = [] + selected = null + run() {} + runLots() {} + add() {} + update() {} + clear() {} + swapRows() {} + select() {} + remove() {} + } + export default new MyStore() + ` + + const result = transformCompiledStoreModule(input, 'MyStore.ts') + assert.ok(result) + assert.strictEqual(result.changed, true) + assert.match(result.code, /extends Compiled(?:Lean)?Store/) }) it('should fall back when source code contains static Store. calls', () => { @@ -43,10 +68,10 @@ describe('transformCompiledStoreModule', () => { const result = transformCompiledStoreModule(input, 'MyStore.ts') assert.ok(result) assert.strictEqual(result.changed, false) - assert.doesNotMatch(result.code, /CompiledStore/) + assert.doesNotMatch(result.code, /extends Compiled(?:Lean)?Store/) }) - it('should fall back when Store. is called on the same line after // in string literal', () => { + it('should fall back when Store. is called after // inside a string literal', () => { const input = ` import { Store } from '@geajs/core' const url = "https://example.com"; Store.someMethod() @@ -59,5 +84,6 @@ describe('transformCompiledStoreModule', () => { const result = transformCompiledStoreModule(input, 'MyStore.ts') assert.ok(result) assert.strictEqual(result.changed, false) + assert.doesNotMatch(result.code, /extends Compiled(?:Lean)?Store/) }) }) \ No newline at end of file From ef2e2b93e7cba5cc6f61792f25f6b625230cae52 Mon Sep 17 00:00:00 2001 From: koharxnp Date: Fri, 21 Aug 2026 10:50:11 +0900 Subject: [PATCH 4/4] fix(vite-plugin-gea): support optional chaining for Store member access in fallback check - Add t.isOptionalMemberExpression check to hasUnsafeStorePattern to handle Store?.method() - Add unit test to verify fallback behavior when optional store access is present --- .../closure-codegen/transform/transform-store.ts | 2 +- .../tests/closure-codegen/transform-store.test.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts index 1b8d742..9ee87b2 100644 --- a/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts +++ b/packages/vite-plugin-gea/src/closure-codegen/transform/transform-store.ts @@ -37,7 +37,7 @@ function hasUnsafeStorePattern(ast: File, storeLocalName: string): boolean { } if ( - t.isMemberExpression(node) && + (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) && t.isIdentifier(node.object, { name: storeLocalName }) ) { unsafe = true diff --git a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts index 59ec9e0..18abb8e 100644 --- a/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts +++ b/packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts @@ -71,6 +71,20 @@ describe('transformCompiledStoreModule', () => { assert.doesNotMatch(result.code, /extends Compiled(?:Lean)?Store/) }) + it('should fall back when optional Store member access (Store?.someMethod) is present', () => { + const input = ` + import { Store } from '@geajs/core' + Store?.someMethod() + export class MyStore extends Store {} + export default new MyStore() + ` + + const result = transformCompiledStoreModule(input, 'MyStore.ts') + assert.ok(result) + assert.strictEqual(result.changed, false) + assert.doesNotMatch(result.code, /extends Compiled(?:Lean)?Store/) + }) + it('should fall back when Store. is called after // inside a string literal', () => { const input = ` import { Store } from '@geajs/core'