prodex 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +369 -146
- package/dist/app/dependency-source-provider.d.ts +2 -0
- package/dist/app/dependency-source-provider.js +66 -0
- package/dist/app/entry-resolver.d.ts +7 -0
- package/dist/app/entry-resolver.js +26 -0
- package/dist/app/execute-run.d.ts +2 -2
- package/dist/app/execute-run.js +138 -36
- package/dist/app/git-source-provider.d.ts +4 -0
- package/dist/app/git-source-provider.js +551 -0
- package/dist/app/grep-source-provider.d.ts +2 -0
- package/dist/app/grep-source-provider.js +226 -0
- package/dist/app/planner.d.ts +12 -0
- package/dist/app/planner.js +106 -0
- package/dist/app/planners/attachment-options.d.ts +2 -0
- package/dist/app/planners/attachment-options.js +39 -0
- package/dist/app/planners/git-plan.d.ts +13 -0
- package/dist/app/planners/git-plan.js +141 -0
- package/dist/app/planners/grep-plan.d.ts +13 -0
- package/dist/app/planners/grep-plan.js +115 -0
- package/dist/app/planners/list-utils.d.ts +1 -0
- package/dist/app/planners/list-utils.js +6 -0
- package/dist/app/planners/pack-plan.d.ts +13 -0
- package/dist/app/planners/pack-plan.js +65 -0
- package/dist/app/planners/scope-plan.d.ts +17 -0
- package/dist/app/planners/scope-plan.js +143 -0
- package/dist/app/planners/trace-plan.d.ts +13 -0
- package/dist/app/planners/trace-plan.js +77 -0
- package/dist/app/source-collector.d.ts +5 -0
- package/dist/app/source-collector.js +33 -0
- package/dist/app/target-resolver.d.ts +7 -0
- package/dist/app/target-resolver.js +110 -0
- package/dist/cli/cli-input.js +70 -19
- package/dist/cli/flag-specs.d.ts +7 -3
- package/dist/cli/flag-specs.js +58 -7
- package/dist/cli/help-specs.d.ts +205 -0
- package/dist/cli/help-specs.js +136 -0
- package/dist/cli/help.js +107 -28
- package/dist/cli/report-command.js +59 -26
- package/dist/commands/{run-command.d.ts → git-command.d.ts} +3 -3
- package/dist/commands/git-command.js +7 -0
- package/dist/commands/grep-command.d.ts +11 -0
- package/dist/commands/grep-command.js +7 -0
- package/dist/commands/pack-command.d.ts +11 -0
- package/dist/commands/pack-command.js +7 -0
- package/dist/commands/scope-command.d.ts +12 -0
- package/dist/commands/scope-command.js +7 -0
- package/dist/commands/shared-runner.d.ts +12 -0
- package/dist/commands/shared-runner.js +30 -0
- package/dist/commands/trace-command.d.ts +11 -0
- package/dist/commands/trace-command.js +7 -0
- package/dist/config/default-config.js +7 -11
- package/dist/config/load.d.ts +1 -0
- package/dist/config/load.js +269 -4
- package/dist/config/migration/detect.d.ts +2 -1
- package/dist/config/migration/detect.js +10 -2
- package/dist/config/migration/messages.js +12 -11
- package/dist/config/migration/transform.js +77 -32
- package/dist/filesystem/binary.d.ts +2 -0
- package/dist/filesystem/binary.js +30 -0
- package/dist/filesystem/entry-discovery.d.ts +14 -0
- package/dist/filesystem/entry-discovery.js +78 -0
- package/dist/filesystem/file-set.d.ts +7 -0
- package/dist/filesystem/file-set.js +57 -0
- package/dist/filesystem/glob-scan.d.ts +6 -2
- package/dist/filesystem/glob-scan.js +12 -7
- package/dist/filesystem/path-patterns.d.ts +20 -0
- package/dist/filesystem/path-patterns.js +70 -0
- package/dist/index.js +51 -29
- package/dist/output/markdown-sections.d.ts +5 -0
- package/dist/output/markdown-sections.js +103 -0
- package/dist/output/markdown-toc.d.ts +31 -0
- package/dist/output/markdown-toc.js +287 -0
- package/dist/output/markdown.d.ts +5 -8
- package/dist/output/markdown.js +80 -127
- package/dist/output/produce-output.d.ts +4 -1
- package/dist/output/produce-output.js +6 -7
- package/dist/output/render-helpers.d.ts +4 -0
- package/dist/output/render-helpers.js +14 -0
- package/dist/output/text.d.ts +2 -2
- package/dist/output/text.js +47 -8
- package/dist/resolvers/js/resolve-alias.js +10 -41
- package/dist/resolvers/php/bindings.js +6 -6
- package/dist/resolvers/php/extract-imports.d.ts +19 -7
- package/dist/resolvers/php/extract-imports.js +146 -26
- package/dist/resolvers/php/php-resolver.js +55 -24
- package/dist/resolvers/php/psr4.d.ts +1 -1
- package/dist/resolvers/php/psr4.js +7 -1
- package/dist/runtime/shell-command-runner.d.ts +5 -0
- package/dist/runtime/shell-command-runner.js +118 -0
- package/dist/tracing/collect-trace.d.ts +10 -0
- package/dist/tracing/collect-trace.js +23 -0
- package/dist/tracing/exclude.d.ts +0 -5
- package/dist/tracing/exclude.js +16 -12
- package/dist/tracing/follow-chain.js +2 -2
- package/dist/types/app.types.d.ts +38 -8
- package/dist/types/artifact.types.d.ts +50 -0
- package/dist/types/artifact.types.js +2 -0
- package/dist/types/cli.types.d.ts +52 -4
- package/dist/types/config.types.d.ts +37 -20
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/output.types.d.ts +12 -3
- package/dist/types/planner.types.d.ts +76 -0
- package/dist/types/planner.types.js +2 -0
- package/dist/types/resolver.types.d.ts +1 -1
- package/dist/types/tracing.types.d.ts +0 -11
- package/package.json +2 -2
- package/schema/prodex.schema.json +59 -41
- package/dist/app/run-plans.d.ts +0 -11
- package/dist/app/run-plans.js +0 -61
- package/dist/commands/profiles-command.d.ts +0 -6
- package/dist/commands/profiles-command.js +0 -16
- package/dist/commands/run-command.js +0 -17
- package/dist/config/build-config.d.ts +0 -13
- package/dist/config/build-config.js +0 -127
- package/dist/tracing/include-files.d.ts +0 -2
- package/dist/tracing/include-files.js +0 -36
- package/dist/tracing/trace-run.d.ts +0 -2
- package/dist/tracing/trace-run.js +0 -21
|
@@ -12,13 +12,12 @@ const render_constants_1 = require("./render-constants");
|
|
|
12
12
|
const markdown_1 = require("./markdown");
|
|
13
13
|
const text_1 = require("./text");
|
|
14
14
|
const path_2 = require("../filesystem/path");
|
|
15
|
-
async function produceOutput({ name,
|
|
16
|
-
const { output: { format, versioned, dir }, } = cfg;
|
|
15
|
+
async function produceOutput({ name, payload, format, dir, versioned, }) {
|
|
17
16
|
let outputBase = (0, path_2.sanitizeFileName)(name || "combined");
|
|
18
17
|
outputBase = `${outputBase}-${render_constants_1.SUFFIX}`;
|
|
19
18
|
if (versioned)
|
|
20
19
|
outputBase = `${outputBase}_${(0, naming_1.shortTimestamp)()}`;
|
|
21
|
-
const outputDir = path_1.default.isAbsolute(dir) ? dir : path_1.default.join(
|
|
20
|
+
const outputDir = path_1.default.isAbsolute(dir) ? dir : path_1.default.join(payload.root, dir);
|
|
22
21
|
try {
|
|
23
22
|
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
24
23
|
}
|
|
@@ -26,10 +25,10 @@ async function produceOutput({ name, files, cfg }) {
|
|
|
26
25
|
logger_1.logger.warn("Could not create output directory:", outputDir);
|
|
27
26
|
}
|
|
28
27
|
const outputPath = path_1.default.join(outputDir, `${outputBase}.${format}`);
|
|
29
|
-
const sorted = [...files].sort((a, b) => a.localeCompare(b));
|
|
30
28
|
const content = format === "txt"
|
|
31
|
-
?
|
|
32
|
-
: (0, markdown_1.renderTraceMd)(
|
|
29
|
+
? (0, text_1.renderTxt)(payload)
|
|
30
|
+
: (0, markdown_1.renderTraceMd)(payload).content;
|
|
33
31
|
fs_1.default.writeFileSync(outputPath, content, "utf8");
|
|
34
|
-
|
|
32
|
+
const outputSizeBytes = Buffer.byteLength(content, "utf8");
|
|
33
|
+
return { outputPath, outputSizeBytes };
|
|
35
34
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLayoutOrder = getLayoutOrder;
|
|
4
|
+
exports.formatExitCode = formatExitCode;
|
|
5
|
+
exports.formatTimeout = formatTimeout;
|
|
6
|
+
function getLayoutOrder(commandKind) {
|
|
7
|
+
return commandKind === "git" ? "sections-first" : "files-first";
|
|
8
|
+
}
|
|
9
|
+
function formatExitCode(code) {
|
|
10
|
+
return code !== null ? String(code) : "null";
|
|
11
|
+
}
|
|
12
|
+
function formatTimeout(timedOut) {
|
|
13
|
+
return timedOut ? "yes" : "no";
|
|
14
|
+
}
|
package/dist/output/text.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function renderTxt(
|
|
1
|
+
import type { ArtifactPayload } from "../types";
|
|
2
|
+
export declare function renderTxt(payload: ArtifactPayload): string;
|
package/dist/output/text.js
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tocTxt = tocTxt;
|
|
4
3
|
exports.renderTxt = renderTxt;
|
|
5
4
|
const read_file_1 = require("../filesystem/read-file");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const render_helpers_1 = require("./render-helpers");
|
|
6
|
+
function renderTxt(payload) {
|
|
7
|
+
const root = payload.root;
|
|
8
|
+
const sorted = [...payload.files].sort((a, b) => a.path.localeCompare(b.path));
|
|
9
|
+
const contextLines = [];
|
|
10
|
+
if (payload.metadata?.commandKind === "trace" && payload.metadata.targets && payload.metadata.targets.length > 0) {
|
|
11
|
+
const relativeEntries = payload.metadata.entries.map(e => (0, read_file_1.rel)(e, root));
|
|
12
|
+
contextLines.push("##==== Trace Target Context ====", `## - Requested Target(s): ${payload.metadata.targets.join(", ")}`, `## - Resolved Starting Point(s): ${relativeEntries.join(", ") || "none"}`, `## - Traversal Depth: ${payload.metadata.depth !== undefined ? payload.metadata.depth : "none"}`, "");
|
|
13
|
+
}
|
|
14
|
+
const layoutOrder = (0, render_helpers_1.getLayoutOrder)(payload.metadata?.commandKind);
|
|
15
|
+
const isFileFirst = layoutOrder === "files-first";
|
|
16
|
+
const sectionToc = (payload.sections ?? []).map((sec) => "## - Section: " + sec.title);
|
|
17
|
+
const fileToc = sorted.map((file) => "## - File: " + (0, read_file_1.rel)(file.path, root));
|
|
18
|
+
const tocParts = isFileFirst ? [...fileToc, ...sectionToc] : [...sectionToc, ...fileToc];
|
|
19
|
+
const toc = ["##==== Combined Scope ====", ...contextLines, ...tocParts].join("\n") + "\n\n";
|
|
20
|
+
const genericSections = (payload.sections ?? [])
|
|
21
|
+
.map((sec) => {
|
|
22
|
+
return ["##==== section: " + sec.title + " ====", "##region " + sec.title, sec.content, "##endregion", ""].join("\n");
|
|
23
|
+
})
|
|
24
|
+
.join("");
|
|
25
|
+
const fileSections = sorted
|
|
26
|
+
.map((file) => {
|
|
27
|
+
const relativePath = (0, read_file_1.rel)(file.path, root);
|
|
28
|
+
const code = file.readError ? `Error reading file: ${file.readError}` : file.content;
|
|
29
|
+
return ["##==== path: " + relativePath + " ====", "##region " + relativePath, code, "##endregion", ""].join("\n");
|
|
30
|
+
})
|
|
31
|
+
.join("");
|
|
32
|
+
const cmdAttachments = renderTxtCmdResults(payload.commandOutputs, root);
|
|
33
|
+
const bodyParts = isFileFirst
|
|
34
|
+
? [toc, fileSections, cmdAttachments, genericSections]
|
|
35
|
+
: [toc, genericSections, fileSections, cmdAttachments];
|
|
36
|
+
return bodyParts.join("");
|
|
9
37
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
38
|
+
function renderTxtCmdResults(cmdResults, root = process.cwd()) {
|
|
39
|
+
if (!cmdResults || cmdResults.length === 0)
|
|
40
|
+
return "";
|
|
41
|
+
const lines = ["\n##==== Command Attachments ===="];
|
|
42
|
+
for (let i = 0; i < cmdResults.length; i++) {
|
|
43
|
+
const res = cmdResults[i];
|
|
44
|
+
const statusStr = res.status;
|
|
45
|
+
lines.push(`## Command ${i + 1}`, `## - Command: ${res.command}`, `## - Directory: ${(0, read_file_1.rel)(res.cwd, root) || "."}`, `## - Status: ${statusStr}`, `## - Exit Code: ${(0, render_helpers_1.formatExitCode)(res.exitCode)}`, `## - Duration: ${res.durationMs}ms`, `## - Timeout State: ${res.timedOut ? "Yes" : "No"}`, `## - Output:`);
|
|
46
|
+
if (res.errorMessage) {
|
|
47
|
+
lines.push(`## - Error Message: ${res.errorMessage}`);
|
|
48
|
+
}
|
|
49
|
+
lines.push(res.combinedOutput);
|
|
50
|
+
}
|
|
51
|
+
lines.push("##==== End Command Attachments ====\n");
|
|
52
|
+
return lines.join("\n");
|
|
14
53
|
}
|
|
@@ -5,48 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.resolveAliasPath = resolveAliasPath;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const cache_manager_1 = require("../../cache/cache-manager");
|
|
9
|
-
const cache_keys_1 = require("../../cache/cache-keys");
|
|
10
|
-
const glob_scan_1 = require("../../filesystem/glob-scan");
|
|
11
|
-
const read_file_1 = require("../../filesystem/read-file");
|
|
12
|
-
const path_2 = require("../../filesystem/path");
|
|
13
8
|
async function resolveAliasPath(specifier, root, cfg) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
// 2️⃣ Check cached aliases
|
|
26
|
-
const cached = cache_manager_1.CacheManager.get(cache_keys_1.CACHE_KEYS.ALIASES, aliasKey);
|
|
27
|
-
if (cached) {
|
|
28
|
-
const relPart = remainder.replace(/^\/+/, "");
|
|
29
|
-
return path_1.default.resolve(root, cached, relPart);
|
|
30
|
-
}
|
|
31
|
-
// 3️⃣ Fallback discovery with Fast-Glob
|
|
32
|
-
const stripped = remainder;
|
|
33
|
-
const hasExt = /\.[a-z0-9]+$/i.test(stripped);
|
|
34
|
-
const patterns = hasExt ? [`**/${stripped}`] : [`**/${stripped}.*`, `**/${stripped}/index.*`];
|
|
35
|
-
const { files: matches } = await (0, glob_scan_1.globScan)(patterns, { cwd: root });
|
|
36
|
-
if (matches.length) {
|
|
37
|
-
const resolvedMatch = resolveMatches(matches, remainder);
|
|
38
|
-
if (!resolvedMatch)
|
|
39
|
-
return null;
|
|
40
|
-
const relPath = (0, read_file_1.rel)(resolvedMatch, cfg.root);
|
|
41
|
-
cache_manager_1.CacheManager.set(cache_keys_1.CACHE_KEYS.ALIASES, aliasKey, relPath);
|
|
42
|
-
return relPath;
|
|
9
|
+
const knownAliases = cfg.aliases || {};
|
|
10
|
+
const aliasKeys = Object.keys(knownAliases).sort((a, b) => b.length - a.length);
|
|
11
|
+
for (const aliasKey of aliasKeys) {
|
|
12
|
+
if (specifier === aliasKey) {
|
|
13
|
+
return path_1.default.resolve(root, knownAliases[aliasKey]);
|
|
14
|
+
}
|
|
15
|
+
if (specifier.startsWith(aliasKey + "/")) {
|
|
16
|
+
const remainder = specifier.slice(aliasKey.length + 1);
|
|
17
|
+
return path_1.default.resolve(root, knownAliases[aliasKey], remainder);
|
|
18
|
+
}
|
|
43
19
|
}
|
|
44
20
|
return null;
|
|
45
21
|
}
|
|
46
|
-
function resolveMatches(matches, remainder) {
|
|
47
|
-
const foundFile = (0, path_2.normalizePath)(matches[0]);
|
|
48
|
-
const dSplit = foundFile.split(remainder);
|
|
49
|
-
if (dSplit.length < 2)
|
|
50
|
-
return "";
|
|
51
|
-
return (0, path_2.normalizePath)(dSplit[0]);
|
|
52
|
-
}
|
|
@@ -41,14 +41,14 @@ function loadLaravelBindings(root) {
|
|
|
41
41
|
const short = fqcn.split("\\").pop();
|
|
42
42
|
importMap[short] = fqcn;
|
|
43
43
|
}
|
|
44
|
-
// 2️⃣ Extract bindings
|
|
45
|
-
const bindRe = /\$this->app->(?:bind|singleton)\s*\(\s
|
|
44
|
+
// 2️⃣ Extract bindings
|
|
45
|
+
const bindRe = /\$this->app->(?:bind|singleton)\s*\(\s*\\?([A-Za-z0-9_\\]+)::class\s*,\s*\\?([A-Za-z0-9_\\]+)::class/g;
|
|
46
46
|
let m;
|
|
47
47
|
while ((m = bindRe.exec(code))) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const ifaceFull = importMap[
|
|
51
|
-
const implFull = importMap[
|
|
48
|
+
const ifaceRaw = m[1].replace(/^\\+/, "");
|
|
49
|
+
const implRaw = m[2].replace(/^\\+/, "");
|
|
50
|
+
const ifaceFull = importMap[ifaceRaw] || ifaceRaw;
|
|
51
|
+
const implFull = importMap[implRaw] || implRaw;
|
|
52
52
|
logger_1.logger.debug(`[laravel-bindings] ${file} => ${ifaceFull} => ${implFull}`);
|
|
53
53
|
bindings[ifaceFull] = implFull;
|
|
54
54
|
}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
import type { PhpResolverCtx } from "../../types";
|
|
1
2
|
/**
|
|
2
|
-
* Extracts
|
|
3
|
-
*
|
|
4
|
-
* -
|
|
5
|
-
* - use
|
|
3
|
+
* Extracts PHP use statements and builds an alias/ShortName to FQCN mapping.
|
|
4
|
+
* Handles:
|
|
5
|
+
* - use Foo\Bar; => { Bar: "Foo\\Bar" }
|
|
6
|
+
* - use Foo\Bar as Baz; => { Baz: "Foo\\Bar" }
|
|
7
|
+
* - use App\Models\{User, Team as Group}; => { User: "App\\Models\\User", Group: "App\\Models\\Team" }
|
|
6
8
|
*/
|
|
7
|
-
export declare function
|
|
9
|
+
export declare function extractPhpUseMap(code: string): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Extracts class references, type hints, static calls, and require/include paths.
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractPhpReferences(code: string): Set<string>;
|
|
8
14
|
/**
|
|
9
15
|
* Expands grouped `use` imports into individual fully qualified names.
|
|
10
|
-
* Example:
|
|
11
|
-
* "App\\Models\\{User, Team}" to ["App\\Models\\User", "App\\Models\\Team"]
|
|
12
16
|
*/
|
|
13
17
|
export declare function expandGroupedUses(raw: Set<string>): Set<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves a raw PHP reference to a fully qualified class name or path.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolvePhpReference(reference: string, currentNamespace: string | null, useMap: Record<string, string>, ctx: PhpResolverCtx): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Backward-compatible wrapper for extracting raw PHP imports.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractPhpImports(code: string): Set<string>;
|
|
@@ -1,47 +1,122 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.extractPhpUseMap = extractPhpUseMap;
|
|
4
|
+
exports.extractPhpReferences = extractPhpReferences;
|
|
4
5
|
exports.expandGroupedUses = expandGroupedUses;
|
|
6
|
+
exports.resolvePhpReference = resolvePhpReference;
|
|
7
|
+
exports.extractPhpImports = extractPhpImports;
|
|
5
8
|
/**
|
|
6
|
-
* Extracts
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* - use
|
|
9
|
+
* Extracts PHP use statements and builds an alias/ShortName to FQCN mapping.
|
|
10
|
+
* Handles:
|
|
11
|
+
* - use Foo\Bar; => { Bar: "Foo\\Bar" }
|
|
12
|
+
* - use Foo\Bar as Baz; => { Baz: "Foo\\Bar" }
|
|
13
|
+
* - use App\Models\{User, Team as Group}; => { User: "App\\Models\\User", Group: "App\\Models\\Team" }
|
|
10
14
|
*/
|
|
11
|
-
function
|
|
15
|
+
function extractPhpUseMap(code) {
|
|
16
|
+
const useMap = {};
|
|
17
|
+
// Match statements like: use App\Models\User; or use App\Models\{User, Team as Group};
|
|
18
|
+
// Skip "use function" or "use const"
|
|
19
|
+
const useStmtRe = /\buse\s+(?!(?:function|const)\s+)([^;]+);/g;
|
|
20
|
+
let match;
|
|
21
|
+
while ((match = useStmtRe.exec(code))) {
|
|
22
|
+
const block = match[1].trim();
|
|
23
|
+
// Check if it's a grouped use statement
|
|
24
|
+
const groupMatch = block.match(/^(.+?)\s*{([^}]+)}/);
|
|
25
|
+
if (groupMatch) {
|
|
26
|
+
const base = groupMatch[1].trim().replace(/\\+$/, "");
|
|
27
|
+
const items = groupMatch[2].split(",").map((i) => i.trim()).filter(Boolean);
|
|
28
|
+
for (const item of items) {
|
|
29
|
+
const asMatch = item.match(/^(.+?)\s+as\s+(.+)$/i);
|
|
30
|
+
if (asMatch) {
|
|
31
|
+
const fqcn = `${base}\\${asMatch[1].trim()}`;
|
|
32
|
+
const alias = asMatch[2].trim();
|
|
33
|
+
useMap[alias] = fqcn;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const fqcn = `${base}\\${item}`;
|
|
37
|
+
const shortName = item.split("\\").pop();
|
|
38
|
+
useMap[shortName] = fqcn;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Single or comma-separated use statements (e.g. "Foo\Bar, Foo\Baz")
|
|
44
|
+
const parts = block.split(",").map((p) => p.trim()).filter(Boolean);
|
|
45
|
+
for (const part of parts) {
|
|
46
|
+
const asMatch = part.match(/^(.+?)\s+as\s+(.+)$/i);
|
|
47
|
+
if (asMatch) {
|
|
48
|
+
const fqcn = asMatch[1].trim();
|
|
49
|
+
const alias = asMatch[2].trim();
|
|
50
|
+
useMap[alias] = fqcn;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const fqcn = part;
|
|
54
|
+
const shortName = part.split("\\").pop();
|
|
55
|
+
useMap[shortName] = fqcn;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return useMap;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Extracts class references, type hints, static calls, and require/include paths.
|
|
64
|
+
*/
|
|
65
|
+
function extractPhpReferences(code) {
|
|
12
66
|
const out = new Set();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
67
|
+
// require/include patterns
|
|
68
|
+
const requireRe = /\b(?:require|include|require_once|include_once)\s*\(?['"]([^'"]+)['"]\)?/g;
|
|
69
|
+
let m;
|
|
70
|
+
while ((m = requireRe.exec(code))) {
|
|
71
|
+
const val = m[1];
|
|
72
|
+
if (val)
|
|
73
|
+
out.add(val);
|
|
74
|
+
}
|
|
75
|
+
// Detect class references (e.g. new User(), User::class, User::method())
|
|
76
|
+
const classPatterns = [
|
|
77
|
+
/\bnew\s+([A-Za-z0-9_\\]+)\b/g,
|
|
78
|
+
/\b([A-Za-z0-9_\\]+)::class\b/g,
|
|
79
|
+
/\b([A-Za-z0-9_\\]+)::[A-Za-z_]/g,
|
|
80
|
+
/:\s*([A-Za-z0-9_\\]+)\b/g
|
|
81
|
+
];
|
|
82
|
+
for (const r of classPatterns) {
|
|
16
83
|
while ((m = r.exec(code))) {
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
|
|
84
|
+
const className = m[1].trim();
|
|
85
|
+
if (/^[0-9]+$/.test(className) || ["self", "parent", "static"].includes(className.toLowerCase())) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
out.add(className);
|
|
20
89
|
}
|
|
21
90
|
}
|
|
22
|
-
// Detect short class names
|
|
23
|
-
const shortClassPatterns = [/\bnew\s+([A-Z][A-Za-z0-9_]*)\b/g, /\b([A-Z][A-Za-z0-9_]*)::class\b/g, /\b([A-Z][A-Za-z0-9_]*)::[A-Za-z_]/g, /:\s*([A-Z][A-Za-z0-9_]*)\b/g];
|
|
24
|
-
// Direct matches
|
|
25
|
-
for (const r of shortClassPatterns) {
|
|
26
|
-
let m;
|
|
27
|
-
while ((m = r.exec(code)))
|
|
28
|
-
out.add(m[1]);
|
|
29
|
-
}
|
|
30
91
|
// Type-hinted parameters in function signatures
|
|
31
|
-
let m;
|
|
32
92
|
const paramPattern = /\(([^)]*)\)/g;
|
|
33
93
|
while ((m = paramPattern.exec(code))) {
|
|
34
94
|
const block = m[1];
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
const params = block.split(",").map((p) => p.trim()).filter(Boolean);
|
|
96
|
+
for (const param of params) {
|
|
97
|
+
const parts = param.split("$");
|
|
98
|
+
if (parts.length > 1) {
|
|
99
|
+
const typePart = parts[0].trim();
|
|
100
|
+
if (typePart) {
|
|
101
|
+
const types = typePart.match(/\b([A-Za-z0-9_\\]+)\b/g);
|
|
102
|
+
if (types) {
|
|
103
|
+
for (const t of types) {
|
|
104
|
+
const trimmedT = t.trim();
|
|
105
|
+
if (trimmedT &&
|
|
106
|
+
!/^[0-9]+$/.test(trimmedT) &&
|
|
107
|
+
!["self", "parent", "static", "array", "callable", "string", "int", "bool", "float", "iterable", "object"].includes(trimmedT.toLowerCase())) {
|
|
108
|
+
out.add(trimmedT);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
38
115
|
}
|
|
39
116
|
return out;
|
|
40
117
|
}
|
|
41
118
|
/**
|
|
42
119
|
* Expands grouped `use` imports into individual fully qualified names.
|
|
43
|
-
* Example:
|
|
44
|
-
* "App\\Models\\{User, Team}" to ["App\\Models\\User", "App\\Models\\Team"]
|
|
45
120
|
*/
|
|
46
121
|
function expandGroupedUses(raw) {
|
|
47
122
|
const out = new Set();
|
|
@@ -61,3 +136,48 @@ function expandGroupedUses(raw) {
|
|
|
61
136
|
}
|
|
62
137
|
return out;
|
|
63
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolves a raw PHP reference to a fully qualified class name or path.
|
|
141
|
+
*/
|
|
142
|
+
function resolvePhpReference(reference, currentNamespace, useMap, ctx) {
|
|
143
|
+
if (!reference || typeof reference !== "string")
|
|
144
|
+
return null;
|
|
145
|
+
if (reference.startsWith(".") || reference.includes("/") || reference.endsWith(".php")) {
|
|
146
|
+
return reference;
|
|
147
|
+
}
|
|
148
|
+
const segments = reference.split("\\");
|
|
149
|
+
const firstSegment = segments[0];
|
|
150
|
+
if (useMap[firstSegment]) {
|
|
151
|
+
const remainder = segments.slice(1).join("\\");
|
|
152
|
+
let resolved = remainder ? `${useMap[firstSegment]}\\${remainder}` : useMap[firstSegment];
|
|
153
|
+
resolved = resolved.replace(/^\\+/, "");
|
|
154
|
+
if (ctx.bindings[resolved])
|
|
155
|
+
resolved = ctx.bindings[resolved];
|
|
156
|
+
return resolved;
|
|
157
|
+
}
|
|
158
|
+
let resolved = reference;
|
|
159
|
+
const isFullyQualified = resolved.includes("\\") || resolved.startsWith("\\");
|
|
160
|
+
if (!isFullyQualified && currentNamespace) {
|
|
161
|
+
resolved = `${currentNamespace}\\${resolved}`;
|
|
162
|
+
}
|
|
163
|
+
resolved = resolved.replace(/^\\+/, "");
|
|
164
|
+
if (ctx.bindings[resolved]) {
|
|
165
|
+
resolved = ctx.bindings[resolved];
|
|
166
|
+
}
|
|
167
|
+
return resolved;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Backward-compatible wrapper for extracting raw PHP imports.
|
|
171
|
+
*/
|
|
172
|
+
function extractPhpImports(code) {
|
|
173
|
+
const out = new Set();
|
|
174
|
+
const useMap = extractPhpUseMap(code);
|
|
175
|
+
for (const fqcn of Object.values(useMap)) {
|
|
176
|
+
out.add(fqcn);
|
|
177
|
+
}
|
|
178
|
+
const refs = extractPhpReferences(code);
|
|
179
|
+
for (const ref of refs) {
|
|
180
|
+
out.add(ref);
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
@@ -24,13 +24,26 @@ async function resolvePhpImports({ cfg, filePath, ctx }) {
|
|
|
24
24
|
return (0, resolver_result_1.emptyResolverResult)();
|
|
25
25
|
const currentNamespace = getCurrentNamespace(code);
|
|
26
26
|
const phpCtx = buildPhpCtx(cfg.root, ctx);
|
|
27
|
-
const
|
|
27
|
+
const useMap = (0, extract_imports_1.extractPhpUseMap)(code);
|
|
28
|
+
const rawReferences = (0, extract_imports_1.extractPhpReferences)(code);
|
|
29
|
+
const candidateImports = new Set();
|
|
30
|
+
for (const fqcn of Object.values(useMap)) {
|
|
31
|
+
candidateImports.add(fqcn);
|
|
32
|
+
}
|
|
33
|
+
for (const ref of rawReferences) {
|
|
34
|
+
const resolvedRef = (0, extract_imports_1.resolvePhpReference)(ref, currentNamespace, useMap, phpCtx);
|
|
35
|
+
if (resolvedRef) {
|
|
36
|
+
candidateImports.add(resolvedRef);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
28
39
|
const stats = (0, resolver_result_1.newResolverStats)();
|
|
29
40
|
const files = [];
|
|
30
|
-
for (const
|
|
31
|
-
const
|
|
32
|
-
|
|
41
|
+
for (const resolvedImport of candidateImports) {
|
|
42
|
+
const isFilePath = resolvedImport.startsWith(".") || resolvedImport.includes("/") || resolvedImport.endsWith(".php");
|
|
43
|
+
const matchesNamespace = startsWithAnyNamespace(resolvedImport, phpCtx.nsKeys);
|
|
44
|
+
if (!isFilePath && !matchesNamespace) {
|
|
33
45
|
continue;
|
|
46
|
+
}
|
|
34
47
|
const resolvedPath = await tryResolvePhpFile(resolvedImport, filePath, phpCtx.psr4);
|
|
35
48
|
stats.expected.add(resolvedImport);
|
|
36
49
|
if (!resolvedPath)
|
|
@@ -48,33 +61,50 @@ function getCurrentNamespace(code) {
|
|
|
48
61
|
const nsMatch = code.match(/\bnamespace\s+([^;]+);/);
|
|
49
62
|
return nsMatch ? nsMatch[1].trim() : null;
|
|
50
63
|
}
|
|
51
|
-
function resolveNamespaceImport(importName, currentNamespace, ctx) {
|
|
52
|
-
if (!importName || typeof importName !== "string")
|
|
53
|
-
return null;
|
|
54
|
-
let resolved = importName;
|
|
55
|
-
const isFullyQualified = resolved.includes("\\") || resolved.startsWith("\\");
|
|
56
|
-
if (!isFullyQualified && currentNamespace)
|
|
57
|
-
resolved = `${currentNamespace}\\${resolved}`;
|
|
58
|
-
if (ctx.bindings[resolved])
|
|
59
|
-
resolved = ctx.bindings[resolved];
|
|
60
|
-
return startsWithAnyNamespace(resolved, ctx.nsKeys) ? resolved : null;
|
|
61
|
-
}
|
|
62
64
|
async function tryResolvePhpFile(imp, fromFile, psr4) {
|
|
63
65
|
const key = `php:${imp}:${fromFile}`;
|
|
64
66
|
const cached = cache_manager_1.CacheManager.get(cache_keys_1.CACHE_KEYS.PHP_FILECACHE, key);
|
|
65
67
|
if (cached !== undefined)
|
|
66
68
|
return cached;
|
|
67
|
-
|
|
69
|
+
if (imp.startsWith(".") || imp.includes("/") || imp.endsWith(".php")) {
|
|
70
|
+
const absolutePath = path_1.default.resolve(path_1.default.dirname(fromFile), imp);
|
|
71
|
+
try {
|
|
72
|
+
const stats = await promises_1.default.stat(absolutePath);
|
|
73
|
+
if (stats.isFile()) {
|
|
74
|
+
const resolved = path_1.default.resolve(absolutePath);
|
|
75
|
+
cache_manager_1.CacheManager.set(cache_keys_1.CACHE_KEYS.PHP_FILECACHE, key, resolved);
|
|
76
|
+
return resolved;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
if (!imp.endsWith(".php")) {
|
|
81
|
+
try {
|
|
82
|
+
const stats = await promises_1.default.stat(absolutePath + ".php");
|
|
83
|
+
if (stats.isFile()) {
|
|
84
|
+
const resolved = path_1.default.resolve(absolutePath + ".php");
|
|
85
|
+
cache_manager_1.CacheManager.set(cache_keys_1.CACHE_KEYS.PHP_FILECACHE, key, resolved);
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
}
|
|
91
|
+
cache_manager_1.CacheManager.set(cache_keys_1.CACHE_KEYS.PHP_FILECACHE, key, null);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const nsKey = Object.keys(psr4).find((candidate) => {
|
|
95
|
+
return imp === candidate || imp.startsWith(candidate + "\\");
|
|
96
|
+
});
|
|
68
97
|
if (!nsKey) {
|
|
69
98
|
cache_manager_1.CacheManager.set(cache_keys_1.CACHE_KEYS.PHP_FILECACHE, key, null);
|
|
70
99
|
return null;
|
|
71
100
|
}
|
|
72
|
-
const relativeImport = (0, path_2.normalizePath)(imp.replace(
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
101
|
+
const relativeImport = (0, path_2.normalizePath)(imp.slice(nsKey.length).replace(/^\\+/, ""));
|
|
102
|
+
const mappedDirs = psr4[nsKey];
|
|
103
|
+
const dirs = Array.isArray(mappedDirs) ? mappedDirs : [mappedDirs];
|
|
104
|
+
const candidates = [];
|
|
105
|
+
for (const dir of dirs) {
|
|
106
|
+
candidates.push(path_1.default.join(dir, relativeImport), path_1.default.join(dir, relativeImport + ".php"), path_1.default.join(dir, relativeImport, "index.php"));
|
|
107
|
+
}
|
|
78
108
|
const results = await Promise.allSettled(candidates.map(async (candidate) => {
|
|
79
109
|
try {
|
|
80
110
|
const stats = await promises_1.default.stat(candidate);
|
|
@@ -97,9 +127,10 @@ function buildPhpCtx(root, prev) {
|
|
|
97
127
|
return { kind: "php", psr4, nsKeys, bindings };
|
|
98
128
|
}
|
|
99
129
|
function startsWithAnyNamespace(imp, nsKeys) {
|
|
100
|
-
for (const nsKey of nsKeys)
|
|
101
|
-
if (imp.startsWith(nsKey))
|
|
130
|
+
for (const nsKey of nsKeys) {
|
|
131
|
+
if (imp === nsKey || imp.startsWith(nsKey + "\\"))
|
|
102
132
|
return true;
|
|
133
|
+
}
|
|
103
134
|
return false;
|
|
104
135
|
}
|
|
105
136
|
function isFulfilledPath(result) {
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Builds a PSR-4 namespace to directory map from composer.json.
|
|
3
3
|
* Returns absolute paths in the map values.
|
|
4
4
|
*/
|
|
5
|
-
export declare function resolvePsr4(root: string): Record<string, string>;
|
|
5
|
+
export declare function resolvePsr4(root: string): Record<string, string | string[]>;
|
|
@@ -27,7 +27,13 @@ function resolvePsr4(root) {
|
|
|
27
27
|
const map = {};
|
|
28
28
|
for (const ns in src) {
|
|
29
29
|
const cleanNs = ns.replace(/\\+$/, "");
|
|
30
|
-
|
|
30
|
+
const val = src[ns];
|
|
31
|
+
if (Array.isArray(val)) {
|
|
32
|
+
map[cleanNs] = val.map((v) => path_1.default.resolve(root, v));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
map[cleanNs] = path_1.default.resolve(root, val);
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
cache_manager_1.CacheManager.set(cache_keys_1.CACHE_KEYS.PHP_PSR4, root, map);
|
|
33
39
|
return map;
|