diff --git a/src/domain/javascript/electronStaticAnalysisIdentity.test.ts b/src/domain/javascript/electronStaticAnalysisIdentity.test.ts index 4ed2377e78..4de895ed36 100644 --- a/src/domain/javascript/electronStaticAnalysisIdentity.test.ts +++ b/src/domain/javascript/electronStaticAnalysisIdentity.test.ts @@ -33,6 +33,10 @@ it.each([ 'const { app } = require("electron"); const runtime = app; runtime.setAppPath("./other");', true, ], + [ + 'const { app } = require("electron"); const copy = { ...{ app } }; copy.app.setAppPath("./other");', + true, + ], [ 'const { app } = require("electron"); const change = app.setAppPath; change("./other");', true, diff --git a/src/domain/javascript/javascriptElectronAppPathChanges.ts b/src/domain/javascript/javascriptElectronAppPathChanges.ts index ae18db9aec..ff0e655859 100644 --- a/src/domain/javascript/javascriptElectronAppPathChanges.ts +++ b/src/domain/javascript/javascriptElectronAppPathChanges.ts @@ -13,6 +13,7 @@ import { semanticObjectPropertyReferences, } from "./javascriptSemanticObjectReferences.js"; import { + semanticCapturedSelectionsAfterPropertyRead, semanticNormalizeCapturedSelections, semanticProjectedSourceFactsAtRead, semanticShiftCapturedSelections, @@ -329,23 +330,10 @@ export const createSemanticElectronAppPathChangeQuery = ( awaitDepths: current.awaitDepths.filter( (depth) => depth !== path?.length, ), - capturedSelections: [ - ...(semanticShiftCapturedSelections( - current.capturedSelections, - source.path.length - 1, - ) ?? []), - ...(source.capturedSelectionAt === undefined - ? [] - : [ - { - at: source.capturedSelectionAt, - depth: source.path.length, - ...(source.ownEnumerable - ? { ownEnumerable: true as const } - : {}), - }, - ]), - ], + capturedSelections: semanticCapturedSelectionsAfterPropertyRead( + current.capturedSelections, + source, + ), }); } } diff --git a/src/domain/javascript/javascriptSemanticProjectedSources.ts b/src/domain/javascript/javascriptSemanticProjectedSources.ts index 722f6b41bd..b4285d5f7d 100644 --- a/src/domain/javascript/javascriptSemanticProjectedSources.ts +++ b/src/domain/javascript/javascriptSemanticProjectedSources.ts @@ -11,6 +11,7 @@ import { unwrapJavaScriptExpression } from "./javascriptAstValues.js"; import { semanticObjectPropertyReferences, semanticArrayPropertyReferences, + type JavaScriptSemanticObjectReference, } from "./javascriptSemanticObjectReferences.js"; import { SEMANTIC_ALIAS_SELECTION_LIMIT } from "./javascriptSemanticResourceLimits.js"; import { @@ -68,6 +69,27 @@ export const semanticShiftCapturedSelections = ( return semanticNormalizeCapturedSelections(shifted); }; +/** Record an object/array property selected when a shallow copy was made. */ +export const semanticCapturedSelectionsAfterPropertyRead = ( + selections: readonly SemanticCapturedSelection[] | undefined, + source: Pick< + JavaScriptSemanticObjectReference, + "path" | "capturedSelectionAt" | "ownEnumerable" + >, +): readonly SemanticCapturedSelection[] => [ + ...(semanticShiftCapturedSelections(selections, source.path.length - 1) ?? + []), + ...(source.capturedSelectionAt === undefined + ? [] + : [ + { + at: source.capturedSelectionAt, + depth: source.path.length, + ...(source.ownEnumerable ? { ownEnumerable: true as const } : {}), + }, + ]), +]; + interface ProjectedSource { readonly node: t.Node; readonly path: SemanticAliasSelectionPath; diff --git a/src/domain/javascript/javascriptSemanticRequireOrigin.ts b/src/domain/javascript/javascriptSemanticRequireOrigin.ts index 2c4477849c..2b40a82e7a 100644 --- a/src/domain/javascript/javascriptSemanticRequireOrigin.ts +++ b/src/domain/javascript/javascriptSemanticRequireOrigin.ts @@ -1,4 +1,5 @@ import { + semanticCapturedSelectionsAfterPropertyRead, semanticProjectedSourceFactsAtRead, semanticProjectedSourceEligibilityAtRead, semanticNormalizeCapturedSelections, @@ -806,23 +807,10 @@ const loaderObjectPropertySources = ( .map((source) => ({ node: source.node, path: [...source.path, ...remaining], - capturedSelections: [ - ...(semanticShiftCapturedSelections( - current.capturedSelections, - source.path.length - 1, - ) ?? []), - ...(source.capturedSelectionAt === undefined - ? [] - : [ - { - at: source.capturedSelectionAt, - depth: source.path.length, - ...(source.ownEnumerable - ? { ownEnumerable: true as const } - : {}), - }, - ]), - ], + capturedSelections: semanticCapturedSelectionsAfterPropertyRead( + current.capturedSelections, + source, + ), })), ); }; diff --git a/tests/acceptance/applications/javascriptReturnedAppPathChanges.test.ts b/tests/acceptance/applications/javascriptReturnedAppPathChanges.test.ts index de571a4f65..f60bca7b10 100644 --- a/tests/acceptance/applications/javascriptReturnedAppPathChanges.test.ts +++ b/tests/acceptance/applications/javascriptReturnedAppPathChanges.test.ts @@ -18,6 +18,12 @@ const cases = [ owners: 1, changed: true, }, + { + name: "shallow-copied app", + setter: "const copy={...{app}};copy.app.setAppPath('./lib');", + owners: 1, + changed: true, + }, { name: "local app", setter: "const getApp=()=>({setAppPath(){}});getApp().setAppPath('./lib');",