Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,50 @@ export interface StoreTransformResult {

export type ResolveImportPath = (importer: string, source: string) => string | null

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.isOptionalMemberExpression(node)) &&
t.isIdentifier(node.object, { name: storeLocalName })
) {
unsafe = true
return
}

if (
t.isNewExpression(node) &&
t.isIdentifier(node.callee, { name: storeLocalName })
) {
Comment thread
KoHaRxnP marked this conversation as resolved.
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(
source: string,
moduleId = '<unknown>',
Expand Down Expand Up @@ -59,7 +103,7 @@ export function transformCompiledStoreModule(
ir: fallbackIrs[0],
irs: fallbackIrs,
}
if (/\b(flushSync|silent|Store\.|new\s+Store\s*\()/.test(source)) {
if (hasUnsafeStorePattern(ast, imported.localName)) {
return fallback
}
// All-or-nothing across the module's store classes: a partial transform
Expand Down
103 changes: 103 additions & 0 deletions packages/vite-plugin-gea/tests/closure-codegen/transform-store.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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, /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', () => {
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 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'
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)
assert.doesNotMatch(result.code, /extends Compiled(?:Lean)?Store/)
})
})