MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / resolveScopedModuleRef

Function resolveScopedModuleRef

src/resolution/frameworks/terraform.ts:129–211  ·  view source on GitHub ↗

* Resolve `module.M: ` by locating the `module "M"` declaration in the * reference's own directory, reading its `source` attribute, and looking the * child symbol up inside that directory.

(
  ref: UnresolvedRef,
  moduleName: string,
  child: string,
  refDir: string,
  context: ResolutionContext
)

Source from the content-addressed store, hash-verified

127 * child symbol up inside that directory.
128 */
129function resolveScopedModuleRef(
130 ref: UnresolvedRef,
131 moduleName: string,
132 child: string,
133 refDir: string,
134 context: ResolutionContext
135): ResolvedRef | null {
136 const decls = context
137 .getNodesByQualifiedName(`module.${moduleName}`)
138 .filter((n) => n.kind === 'module');
139 if (decls.length === 0) return null;
140 // Terraform scoping: the declaration lives in the reference's directory.
141 const decl = decls.find((d) => dirOf(d.filePath) === refDir) ?? (decls.length === 1 ? decls[0]! : null);
142 if (!decl) return null;
143
144 const source = readModuleAttr(decl, 'source', context);
145 if (!source) return null;
146
147 // --- cloudposse/atmos remote-state: module.M.outputs.X where M is the
148 // stack-config remote-state module reading another COMPONENT's state. The
149 // component name is static in the monorepo case (`component = "vpc"` or
150 // "eks/cluster"), so bridge to that component directory's own
151 // `output "X"` — but only when every gate holds: the module source is the
152 // remote-state module, the component is a string literal, and exactly ONE
153 // directory in the repo matches the component name and declares that
154 // output. Anything dynamic or ambiguous stays a visible boundary.
155 if (child.startsWith('remote-output.')) {
156 if (!/\/remote-state(\/|$)/.test(source)) return null;
157 let component = readModuleAttr(decl, 'component', context);
158 if (!component) {
159 // The other half of real-world declarations indirect through a
160 // variable with a literal default in the same directory
161 // (`component = var.vpc_component_name` + `default = "vpc"`) — the
162 // component's declared static wiring. One hop, same literal gate.
163 const viaVar = readNodeSpanMatch(decl, /^\s*component\s*=\s*var\.([A-Za-z0-9_-]+)\s*$/, context);
164 if (viaVar) {
165 const declared = context
166 .getNodesByQualifiedName(`var.${viaVar}`)
167 .filter((n) => dirOf(n.filePath) === dirOf(decl.filePath));
168 if (declared.length === 1) {
169 component = readNodeSpanMatch(declared[0]!, /^\s*default\s*=\s*"([^"]+)"/, context);
170 }
171 }
172 }
173 if (!component) return null;
174 const outName = child.slice('remote-output.'.length);
175 const outs = context
176 .getNodesByQualifiedName(`output.${outName}`)
177 .filter((o) => {
178 const d = dirOf(o.filePath);
179 return d === component || d.endsWith('/' + component);
180 });
181 if (outs.length === 0) return null;
182 const dirs = new Set(outs.map((o) => dirOf(o.filePath)));
183 if (dirs.size > 1) return null; // two directories claim this component name — never guess
184 return { original: ref, targetNodeId: outs[0]!.id, confidence: 0.9, resolvedBy: 'framework' };
185 }
186

Callers 1

resolveFunction · 0.85

Calls 8

readModuleAttrFunction · 0.85
readNodeSpanMatchFunction · 0.85
normalizeRelFunction · 0.85
joinDirsFunction · 0.85
dirOfFunction · 0.70
getAllFilesMethod · 0.65
getNodesInFileMethod · 0.65

Tested by

no test coverage detected