* Resolve a Lua/Luau `require(...)` to its module file. The reference name is * either a dotted module path (`telescope.config` → `telescope/config.lua`) or a * Roblox instance-path leaf (`Signal` from `require(script.Parent.Signal)` → * `Signal.luau`). We try ` .lua|.luau` and ` /init.
(ref: UnresolvedRef, context: ResolutionContext)
| 1427 | * requiring file wins (instance-path requires resolve within the same package). |
| 1428 | */ |
| 1429 | function resolveLuaRequire(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null { |
| 1430 | const name = ref.referenceName; |
| 1431 | if (!name) return null; |
| 1432 | const base = name.includes('.') ? name.replace(/\./g, '/') : name; |
| 1433 | const suffixes = [`${base}.lua`, `${base}.luau`, `${base}/init.lua`, `${base}/init.luau`]; |
| 1434 | const files = context.getAllFiles(); |
| 1435 | const shared = (a: string, b: string): number => { |
| 1436 | let i = 0; |
| 1437 | while (i < a.length && i < b.length && a[i] === b[i]) i++; |
| 1438 | return i; |
| 1439 | }; |
| 1440 | for (const suffix of suffixes) { |
| 1441 | const matches = files.filter((f) => f === suffix || f.endsWith('/' + suffix)); |
| 1442 | if (matches.length === 0) continue; |
| 1443 | matches.sort((x, y) => shared(y, ref.filePath) - shared(x, ref.filePath)); |
| 1444 | const best = matches[0]!; |
| 1445 | if (best === ref.filePath) continue; |
| 1446 | const fileNode = context.getNodesInFile(best).find((n) => n.kind === 'file'); |
| 1447 | if (fileNode) { |
| 1448 | // Confidence ≥ 0.9 so this deterministic path/suffix match wins over |
| 1449 | // name-matching, which otherwise resolves the require to the import node |
| 1450 | // itself (a same-name self-match). |
| 1451 | return { original: ref, targetNodeId: fileNode.id, confidence: 0.9, resolvedBy: 'import' }; |
| 1452 | } |
| 1453 | } |
| 1454 | return null; |
| 1455 | } |
| 1456 | |
| 1457 | function resolveModuleImportToFile( |
| 1458 | ref: UnresolvedRef, |
no test coverage detected