@emeryld/manager 1.3.0 → 1.3.1
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 +65 -0
- package/dist/robot/cli/prompts.js +10 -4
- package/dist/robot/cli/settings.js +16 -0
- package/dist/robot/config.js +11 -0
- package/dist/robot/coordinator.js +10 -6
- package/dist/robot/extractors/classes.js +14 -13
- package/dist/robot/extractors/components.js +17 -10
- package/dist/robot/extractors/constants.js +9 -6
- package/dist/robot/extractors/functions.js +11 -8
- package/dist/robot/extractors/shared.js +6 -1
- package/dist/robot/extractors/types.js +5 -8
- package/dist/robot/serializer.js +97 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,8 +65,73 @@ You can also decide where the report lands: the manager will ask whether to stre
|
|
|
65
65
|
|
|
66
66
|
The `robot metadata` action now starts with the same interactive settings screen as the format checker: pick which kinds of symbols to include, decide whether to limit the scan to exported declarations, and adjust the column width. Use ↑/↓ to move between rows, type new values (comma-separated lists for the kinds), and press Enter once the validation message disappears.
|
|
67
67
|
|
|
68
|
+
The column width now controls how much context is emitted for each declaration. Symbols that start past the configured column keep their name/export status but drop heavyweight fields (IO signatures, docstrings, JSX locations, etc.), so you still see everything in the workspace but only the left-most definitions contribute rich metadata to your LLM prompt.
|
|
69
|
+
|
|
68
70
|
Before the extraction runs you can also choose how to consume the results. The default `console` stream prints the JSON into your terminal, while `editor` writes the report to a temporary file (non-saved) and tries to open it in your editor via your configured `$EDITOR`, `code`, or the OS `open/xdg-open` helpers.
|
|
69
71
|
|
|
72
|
+
To keep the payload more token-friendly, the interactive settings screen now exposes `Condense output for compact JSON` and `Docstring length limit`. Enabling the condensed output replaces the verbose object format with a small `fields` + `rows` array layout, and the docstring limit trims comments to the first N characters (set the value to `0` for unlimited). Set your preferred defaults in `.vscode/settings.json` under `manager.robot.condenseOutput` and `manager.robot.maxDocStringLength`.
|
|
73
|
+
|
|
74
|
+
### Running with arguments
|
|
75
|
+
Run `pnpm manager-cli`, pick the workspace (or “All packages”), then select the `robot metadata` action. The helper walks you through:
|
|
76
|
+
|
|
77
|
+
1. Set the symbol kinds to capture (comma-separated list or `all`).
|
|
78
|
+
2. Toggle `Only exported symbols`, adjust `Maximum columns`, `Condense output for compact JSON`, and `Docstring length limit`.
|
|
79
|
+
3. Confirm the summary to trigger the run and choose whether to stream results to the console or editor.
|
|
80
|
+
|
|
81
|
+
The same knobs live in `.vscode/settings.json` under `manager.robot`, for example:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"manager.robot": {
|
|
86
|
+
"includeKinds": ["function", "type", "const"],
|
|
87
|
+
"exportedOnly": true,
|
|
88
|
+
"maxColumns": 120,
|
|
89
|
+
"condenseOutput": true,
|
|
90
|
+
"maxDocStringLength": 80
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Example output
|
|
96
|
+
`robot metadata` prints a summary followed by the parsed payload. With the default formatting you get readable JSON:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
Robot extraction complete (functions=4 components=1 types=2 consts=0 classes=1)
|
|
100
|
+
Estimated tokens: 72
|
|
101
|
+
Detailed results:
|
|
102
|
+
{
|
|
103
|
+
"functions": [
|
|
104
|
+
{
|
|
105
|
+
"kind": "function",
|
|
106
|
+
"name": "buildManagerConfig",
|
|
107
|
+
"location": { "file": "src/menu.ts", "line": 45, "column": 3 },
|
|
108
|
+
"docString": "Builds the menu configuration for the helper CLI.",
|
|
109
|
+
"exported": true,
|
|
110
|
+
"inputs": ["packages: LoadedPackage[]"],
|
|
111
|
+
"output": "HelperScriptEntry[]"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
...
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Enabling `Condense output for compact JSON` yields a compact payload that reduces tokens even when the detailed rows are long:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
Robot extraction complete (functions=4 components=1 types=2 consts=0 classes=1)
|
|
122
|
+
Estimated tokens: 48
|
|
123
|
+
Detailed results (condensed):
|
|
124
|
+
{
|
|
125
|
+
"version": 1,
|
|
126
|
+
"summary": { "functions": 4, "components": 1, "types": 2, "consts": 0, "classes": 1 },
|
|
127
|
+
"fields": ["kind","file","line","column","name","signature","docString","exported"],
|
|
128
|
+
"rows": [
|
|
129
|
+
["function","src/menu.ts",45,3,"buildManagerConfig","(packages: LoadedPackage[]) => HelperScriptEntry[]","Builds the menu configuration...",true],
|
|
130
|
+
["component","src/ui/banner.ts",12,7,"Banner","(props: BannerProps) => JSX.Element","Lightweight banner used in the hero layout.",true]
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
70
135
|
## Non-interactive release (Codex/CI)
|
|
71
136
|
- **Syntax**: `pnpm manager-cli <pkg|all> --non-interactive [publish flags]`
|
|
72
137
|
- **Requirements**: provide the selection (`<pkg>` or `all`) and one of `--bump <type>`, `--sync <version>`, or `--noop` (skip the version change but still tag/publish). Use `--non-interactive`, `--ci`, `--yes`, or `-y` interchangeably to answer every prompt in the affirmative.
|
|
@@ -35,6 +35,8 @@ async function promptRobotSettingsSequential(defaults) {
|
|
|
35
35
|
includeKinds: await promptKinds(defaults.includeKinds),
|
|
36
36
|
exportedOnly: await promptBoolean('Only consider exported symbols', defaults.exportedOnly),
|
|
37
37
|
maxColumns: await promptNumber('Maximum columns', defaults.maxColumns),
|
|
38
|
+
condenseOutput: await promptBoolean('Condense output for compact JSON', defaults.condenseOutput),
|
|
39
|
+
maxDocStringLength: await promptNumber('Docstring length limit (0 = unlimited)', defaults.maxDocStringLength, { allowZero: true }),
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
async function promptKinds(fallback) {
|
|
@@ -68,17 +70,21 @@ async function promptBoolean(label, fallback) {
|
|
|
68
70
|
console.log(colors.yellow('Please answer "yes" or "no", or leave blank to keep the default.'));
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
|
-
async function promptNumber(label, fallback) {
|
|
73
|
+
async function promptNumber(label, fallback, options) {
|
|
72
74
|
const question = colors.cyan(`${label} [default ${fallback}]: `);
|
|
75
|
+
const min = options?.allowZero ? 0 : 1;
|
|
76
|
+
const description = options?.allowZero ? 'non-negative integer' : 'positive integer';
|
|
73
77
|
while (true) {
|
|
74
78
|
const answer = await askLine(question);
|
|
75
79
|
if (!answer)
|
|
76
80
|
return fallback;
|
|
77
81
|
const parsed = Number(answer);
|
|
78
|
-
if (!Number.isNaN(parsed)
|
|
79
|
-
|
|
82
|
+
if (!Number.isNaN(parsed)) {
|
|
83
|
+
const floored = Math.floor(parsed);
|
|
84
|
+
if (floored >= min)
|
|
85
|
+
return floored;
|
|
80
86
|
}
|
|
81
|
-
console.log(colors.yellow(
|
|
87
|
+
console.log(colors.yellow(`Provide a ${description} or leave blank to keep the default.`));
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
async function promptRobotSettingsInteractive(defaults) {
|
|
@@ -18,6 +18,18 @@ export const SETTING_DESCRIPTORS = [
|
|
|
18
18
|
unit: 'columns',
|
|
19
19
|
type: 'number',
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
key: 'condenseOutput',
|
|
23
|
+
label: 'Condense output for compact JSON',
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'maxDocStringLength',
|
|
28
|
+
label: 'Docstring length limit (0=unlimited)',
|
|
29
|
+
unit: 'characters',
|
|
30
|
+
type: 'number',
|
|
31
|
+
allowZero: true,
|
|
32
|
+
},
|
|
21
33
|
];
|
|
22
34
|
export function formatValue(value, descriptor) {
|
|
23
35
|
if (descriptor.type === 'boolean') {
|
|
@@ -63,6 +75,10 @@ export function validateRobotSettings(settings) {
|
|
|
63
75
|
if (!Number.isFinite(settings.maxColumns) || settings.maxColumns <= 0) {
|
|
64
76
|
return 'Maximum columns must be a positive number.';
|
|
65
77
|
}
|
|
78
|
+
if (!Number.isFinite(settings.maxDocStringLength) ||
|
|
79
|
+
settings.maxDocStringLength < 0) {
|
|
80
|
+
return 'Docstring length limit must be zero or greater.';
|
|
81
|
+
}
|
|
66
82
|
return undefined;
|
|
67
83
|
}
|
|
68
84
|
function parsePositiveInteger(inputValue, options) {
|
package/dist/robot/config.js
CHANGED
|
@@ -9,6 +9,8 @@ export const DEFAULT_ROBOT_SETTINGS = {
|
|
|
9
9
|
includeKinds: [...ROBOT_KINDS],
|
|
10
10
|
exportedOnly: true,
|
|
11
11
|
maxColumns: 160,
|
|
12
|
+
condenseOutput: true,
|
|
13
|
+
maxDocStringLength: 0,
|
|
12
14
|
};
|
|
13
15
|
function coerceNumber(value, fallback) {
|
|
14
16
|
const num = Number(value);
|
|
@@ -28,6 +30,13 @@ function coerceBoolean(value, fallback) {
|
|
|
28
30
|
}
|
|
29
31
|
return fallback;
|
|
30
32
|
}
|
|
33
|
+
function coerceNonNegativeNumber(value, fallback) {
|
|
34
|
+
const num = Number(value);
|
|
35
|
+
if (Number.isNaN(num) || num < 0) {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
return Math.floor(num);
|
|
39
|
+
}
|
|
31
40
|
function coerceKinds(value) {
|
|
32
41
|
const knownKinds = new Set(ROBOT_KINDS);
|
|
33
42
|
if (typeof value === 'string') {
|
|
@@ -75,6 +84,8 @@ export async function loadRobotSettings() {
|
|
|
75
84
|
includeKinds: coerceKinds(record.includeKinds ?? record.kinds),
|
|
76
85
|
exportedOnly: coerceBoolean(record.exportedOnly ?? DEFAULT_ROBOT_SETTINGS.exportedOnly, DEFAULT_ROBOT_SETTINGS.exportedOnly),
|
|
77
86
|
maxColumns: coerceNumber(record.maxColumns ?? DEFAULT_ROBOT_SETTINGS.maxColumns, DEFAULT_ROBOT_SETTINGS.maxColumns),
|
|
87
|
+
condenseOutput: coerceBoolean(record.condenseOutput ?? DEFAULT_ROBOT_SETTINGS.condenseOutput, DEFAULT_ROBOT_SETTINGS.condenseOutput),
|
|
88
|
+
maxDocStringLength: coerceNonNegativeNumber(record.maxDocStringLength ?? DEFAULT_ROBOT_SETTINGS.maxDocStringLength, DEFAULT_ROBOT_SETTINGS.maxDocStringLength),
|
|
78
89
|
};
|
|
79
90
|
}
|
|
80
91
|
catch {
|
|
@@ -13,6 +13,7 @@ import { collectComponents } from './extractors/components.js';
|
|
|
13
13
|
import { collectConstants } from './extractors/constants.js';
|
|
14
14
|
import { collectTypes } from './extractors/types.js';
|
|
15
15
|
import { collectClasses } from './extractors/classes.js';
|
|
16
|
+
import { serializeRobotResult } from './serializer.js';
|
|
16
17
|
export async function runRobot() {
|
|
17
18
|
const defaults = await loadRobotSettings();
|
|
18
19
|
let settings = defaults;
|
|
@@ -111,23 +112,26 @@ async function executeRobotExtraction(settings, exportMode, scanRoot, scanLabel)
|
|
|
111
112
|
consts: result.consts.length,
|
|
112
113
|
classes: result.classes.length,
|
|
113
114
|
};
|
|
114
|
-
const
|
|
115
|
-
const tokenEstimate = estimateTokenCount(
|
|
115
|
+
const serializedResult = serializeRobotResult(result, settings);
|
|
116
|
+
const tokenEstimate = estimateTokenCount(serializedResult);
|
|
116
117
|
result.tokenEstimate = tokenEstimate;
|
|
118
|
+
const detailLabel = settings.condenseOutput
|
|
119
|
+
? 'Detailed results (condensed)'
|
|
120
|
+
: 'Detailed results';
|
|
117
121
|
const summaryText = `Robot extraction complete (functions=${summary.functions} components=${summary.components} types=${summary.types} consts=${summary.consts} classes=${summary.classes})`;
|
|
118
122
|
console.log(colors.green(summaryText));
|
|
119
123
|
console.log(colors.dim(`Estimated tokens: ${tokenEstimate}`));
|
|
120
124
|
if (exportMode === 'console') {
|
|
121
|
-
console.log(colors.dim(
|
|
122
|
-
console.log(
|
|
125
|
+
console.log(colors.dim(detailLabel));
|
|
126
|
+
console.log(serializedResult);
|
|
123
127
|
}
|
|
124
128
|
else {
|
|
125
129
|
await exportReportLines('robot-metadata', 'json', [
|
|
126
130
|
summaryText,
|
|
127
131
|
`Token estimate: ${tokenEstimate}`,
|
|
128
132
|
'',
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
`${detailLabel}:`,
|
|
134
|
+
serializedResult,
|
|
131
135
|
]);
|
|
132
136
|
}
|
|
133
137
|
return result;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getDocString, getLocation, isNodeExported,
|
|
2
|
+
import { EMPTY_LOCATION, getDocString, getLocation, isNodeExported, shouldIncludeContext, } from './shared.js';
|
|
3
3
|
export function collectClasses(options) {
|
|
4
4
|
const { sourceFile } = options.context;
|
|
5
5
|
const records = [];
|
|
@@ -10,26 +10,27 @@ export function collectClasses(options) {
|
|
|
10
10
|
}
|
|
11
11
|
const name = node.name?.text;
|
|
12
12
|
const location = getLocation(node, sourceFile);
|
|
13
|
-
|
|
14
|
-
ts.forEachChild(node, visit);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
13
|
+
const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
|
|
17
14
|
const exported = isNodeExported(node);
|
|
18
15
|
if (options.settings.exportedOnly && !exported) {
|
|
19
16
|
ts.forEachChild(node, visit);
|
|
20
17
|
return;
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
|
|
19
|
+
let extendsText;
|
|
20
|
+
let implementsList;
|
|
21
|
+
if (includeContext) {
|
|
22
|
+
const extendsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword);
|
|
23
|
+
const implementsClause = node.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword);
|
|
24
|
+
extendsText = extendsClause?.types
|
|
25
|
+
.map((expr) => expr.getText(sourceFile))
|
|
26
|
+
.join(', ');
|
|
27
|
+
implementsList = implementsClause?.types.map((expr) => expr.getText(sourceFile));
|
|
28
|
+
}
|
|
28
29
|
records.push({
|
|
29
30
|
kind: 'class',
|
|
30
31
|
name,
|
|
31
|
-
location,
|
|
32
|
-
docString: getDocString(node),
|
|
32
|
+
location: includeContext ? location : EMPTY_LOCATION,
|
|
33
|
+
docString: includeContext ? getDocString(node) : undefined,
|
|
33
34
|
exported,
|
|
34
35
|
extends: extendsText,
|
|
35
36
|
implements: implementsList,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { describeParameter, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported,
|
|
2
|
+
import { describeParameter, EMPTY_LOCATION, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported, shouldIncludeContext, } from './shared.js';
|
|
3
3
|
export function collectComponents(options) {
|
|
4
4
|
const { sourceFile } = options.context;
|
|
5
5
|
const records = [];
|
|
@@ -14,24 +14,31 @@ export function collectComponents(options) {
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
const location = getLocation(node, sourceFile);
|
|
17
|
-
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
17
|
+
const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
|
|
20
18
|
const exported = isNodeExported(node);
|
|
21
19
|
if (options.settings.exportedOnly && !exported)
|
|
22
20
|
return;
|
|
23
|
-
const inputs =
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const inputs = includeContext
|
|
22
|
+
? node.parameters.map((param) => describeParameter(param, sourceFile))
|
|
23
|
+
: [];
|
|
24
|
+
const output = includeContext
|
|
25
|
+
? node.type?.getText(sourceFile)
|
|
26
|
+
: undefined;
|
|
27
|
+
const docString = includeContext ? getDocString(node) : undefined;
|
|
28
|
+
let jsxLocation;
|
|
29
|
+
if (includeContext) {
|
|
30
|
+
const jsxNode = findFirstJsx(node);
|
|
31
|
+
jsxLocation = jsxNode ? getLocation(jsxNode, sourceFile) : location;
|
|
32
|
+
}
|
|
26
33
|
records.push({
|
|
27
34
|
kind: 'component',
|
|
28
35
|
name,
|
|
29
|
-
location,
|
|
30
|
-
docString
|
|
36
|
+
location: includeContext ? location : EMPTY_LOCATION,
|
|
37
|
+
docString,
|
|
31
38
|
exported,
|
|
32
39
|
inputs,
|
|
33
40
|
output,
|
|
34
|
-
jsxLocation
|
|
41
|
+
jsxLocation,
|
|
35
42
|
});
|
|
36
43
|
}
|
|
37
44
|
ts.forEachChild(node, visit);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getDocString, getLocation, isNodeExported,
|
|
2
|
+
import { EMPTY_LOCATION, getDocString, getLocation, isNodeExported, shouldIncludeContext, } from './shared.js';
|
|
3
3
|
export function collectConstants(options) {
|
|
4
4
|
const { sourceFile } = options.context;
|
|
5
5
|
const records = [];
|
|
@@ -23,15 +23,18 @@ export function collectConstants(options) {
|
|
|
23
23
|
if (!ts.isIdentifier(declaration.name))
|
|
24
24
|
return;
|
|
25
25
|
const location = getLocation(declaration.name, sourceFile);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
|
|
27
|
+
const docString = includeContext ? getDocString(declaration) : undefined;
|
|
28
|
+
const value = includeContext
|
|
29
|
+
? declaration.initializer?.getText(sourceFile) ?? 'undefined'
|
|
30
|
+
: '';
|
|
28
31
|
records.push({
|
|
29
32
|
kind: 'const',
|
|
30
33
|
name: declaration.name.text,
|
|
31
|
-
location,
|
|
32
|
-
docString
|
|
34
|
+
location: includeContext ? location : EMPTY_LOCATION,
|
|
35
|
+
docString,
|
|
33
36
|
exported,
|
|
34
|
-
value
|
|
37
|
+
value,
|
|
35
38
|
});
|
|
36
39
|
});
|
|
37
40
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { describeParameter, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported,
|
|
2
|
+
import { describeParameter, EMPTY_LOCATION, getDocString, getFunctionName, getLocation, isComponentFunction, isNodeExported, shouldIncludeContext, } from './shared.js';
|
|
3
3
|
export function collectFunctions(options) {
|
|
4
4
|
const { sourceFile } = options.context;
|
|
5
5
|
const records = [];
|
|
@@ -15,19 +15,22 @@ export function collectFunctions(options) {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
const location = getLocation(node, sourceFile);
|
|
18
|
-
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
18
|
+
const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
|
|
21
19
|
const exported = isNodeExported(node);
|
|
22
20
|
if (options.settings.exportedOnly && !exported)
|
|
23
21
|
return;
|
|
24
|
-
const inputs =
|
|
25
|
-
|
|
22
|
+
const inputs = includeContext
|
|
23
|
+
? node.parameters.map((param) => describeParameter(param, sourceFile))
|
|
24
|
+
: [];
|
|
25
|
+
const output = includeContext
|
|
26
|
+
? node.type?.getText(sourceFile)
|
|
27
|
+
: undefined;
|
|
28
|
+
const docString = includeContext ? getDocString(node) : undefined;
|
|
26
29
|
records.push({
|
|
27
30
|
kind: 'function',
|
|
28
31
|
name,
|
|
29
|
-
location,
|
|
30
|
-
docString
|
|
32
|
+
location: includeContext ? location : EMPTY_LOCATION,
|
|
33
|
+
docString,
|
|
31
34
|
exported,
|
|
32
35
|
inputs,
|
|
33
36
|
output,
|
|
@@ -44,11 +44,16 @@ export function isNodeExported(node) {
|
|
|
44
44
|
return true;
|
|
45
45
|
return false;
|
|
46
46
|
}
|
|
47
|
-
export function
|
|
47
|
+
export function shouldIncludeContext(location, maxColumns) {
|
|
48
48
|
if (maxColumns <= 0)
|
|
49
49
|
return true;
|
|
50
50
|
return location.column <= maxColumns;
|
|
51
51
|
}
|
|
52
|
+
export const EMPTY_LOCATION = {
|
|
53
|
+
file: '',
|
|
54
|
+
line: 0,
|
|
55
|
+
column: 0,
|
|
56
|
+
};
|
|
52
57
|
export function getFunctionName(node) {
|
|
53
58
|
if ('name' in node && node.name && ts.isIdentifier(node.name)) {
|
|
54
59
|
return node.name.text;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getDocString, getLocation, isNodeExported,
|
|
2
|
+
import { EMPTY_LOCATION, getDocString, getLocation, isNodeExported, shouldIncludeContext, } from './shared.js';
|
|
3
3
|
export function collectTypes(options) {
|
|
4
4
|
const { sourceFile } = options.context;
|
|
5
5
|
const records = [];
|
|
@@ -16,10 +16,7 @@ export function collectTypes(options) {
|
|
|
16
16
|
}
|
|
17
17
|
if (typeKind) {
|
|
18
18
|
const location = getLocation(node, sourceFile);
|
|
19
|
-
|
|
20
|
-
ts.forEachChild(node, visit);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
19
|
+
const includeContext = shouldIncludeContext(location, options.settings.maxColumns);
|
|
23
20
|
const exported = isNodeExported(node);
|
|
24
21
|
if (options.settings.exportedOnly && !exported) {
|
|
25
22
|
ts.forEachChild(node, visit);
|
|
@@ -29,11 +26,11 @@ export function collectTypes(options) {
|
|
|
29
26
|
records.push({
|
|
30
27
|
kind: 'type',
|
|
31
28
|
name,
|
|
32
|
-
location,
|
|
33
|
-
docString: getDocString(node),
|
|
29
|
+
location: includeContext ? location : EMPTY_LOCATION,
|
|
30
|
+
docString: includeContext ? getDocString(node) : undefined,
|
|
34
31
|
exported,
|
|
35
32
|
typeKind,
|
|
36
|
-
definition: node.getText(sourceFile),
|
|
33
|
+
definition: includeContext ? node.getText(sourceFile) : '',
|
|
37
34
|
});
|
|
38
35
|
}
|
|
39
36
|
ts.forEachChild(node, visit);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const CONDENSED_FIELDS = [
|
|
2
|
+
'kind',
|
|
3
|
+
'file',
|
|
4
|
+
'line',
|
|
5
|
+
'column',
|
|
6
|
+
'name',
|
|
7
|
+
'signature',
|
|
8
|
+
'docString',
|
|
9
|
+
'exported',
|
|
10
|
+
];
|
|
11
|
+
export function serializeRobotResult(result, settings) {
|
|
12
|
+
if (!settings.condenseOutput) {
|
|
13
|
+
return JSON.stringify(result, null, 2);
|
|
14
|
+
}
|
|
15
|
+
return JSON.stringify(buildCondensedPayload(result, settings));
|
|
16
|
+
}
|
|
17
|
+
function buildCondensedPayload(result, settings) {
|
|
18
|
+
const rows = [];
|
|
19
|
+
const limit = settings.maxDocStringLength;
|
|
20
|
+
for (const record of flattenRecords(result)) {
|
|
21
|
+
rows.push([
|
|
22
|
+
record.kind,
|
|
23
|
+
record.location.file,
|
|
24
|
+
record.location.line,
|
|
25
|
+
record.location.column,
|
|
26
|
+
record.name ?? '',
|
|
27
|
+
buildSignature(record),
|
|
28
|
+
trimDocString(record.docString, limit),
|
|
29
|
+
record.exported,
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
version: 1,
|
|
34
|
+
summary: {
|
|
35
|
+
functions: result.functions.length,
|
|
36
|
+
components: result.components.length,
|
|
37
|
+
types: result.types.length,
|
|
38
|
+
consts: result.consts.length,
|
|
39
|
+
classes: result.classes.length,
|
|
40
|
+
},
|
|
41
|
+
fields: [...CONDENSED_FIELDS],
|
|
42
|
+
rows,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function flattenRecords(result) {
|
|
46
|
+
return [
|
|
47
|
+
...result.functions,
|
|
48
|
+
...result.components,
|
|
49
|
+
...result.types,
|
|
50
|
+
...result.consts,
|
|
51
|
+
...result.classes,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function buildSignature(record) {
|
|
55
|
+
switch (record.kind) {
|
|
56
|
+
case 'function':
|
|
57
|
+
case 'component': {
|
|
58
|
+
const params = record.inputs.join(', ');
|
|
59
|
+
const output = record.output ? ` => ${normalize(record.output)}` : '';
|
|
60
|
+
return `(${params})${output}`;
|
|
61
|
+
}
|
|
62
|
+
case 'type': {
|
|
63
|
+
const displayKind = record.typeKind === 'type-alias'
|
|
64
|
+
? 'type alias'
|
|
65
|
+
: record.typeKind;
|
|
66
|
+
return normalize(`${displayKind} ${record.definition}`);
|
|
67
|
+
}
|
|
68
|
+
case 'const':
|
|
69
|
+
return normalize(record.value);
|
|
70
|
+
case 'class': {
|
|
71
|
+
const parts = [];
|
|
72
|
+
if (record.extends)
|
|
73
|
+
parts.push(`extends ${record.extends}`);
|
|
74
|
+
if (record.implements?.length) {
|
|
75
|
+
parts.push(`implements ${record.implements.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
return normalize(parts.join(' '));
|
|
78
|
+
}
|
|
79
|
+
default:
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function trimDocString(value, limit) {
|
|
84
|
+
if (!value) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
const normalized = normalize(value);
|
|
88
|
+
if (limit <= 0 || normalized.length <= limit) {
|
|
89
|
+
return normalized;
|
|
90
|
+
}
|
|
91
|
+
return `${normalized.slice(0, limit)}...`;
|
|
92
|
+
}
|
|
93
|
+
function normalize(value) {
|
|
94
|
+
return (value ?? '')
|
|
95
|
+
.replace(/\s+/g, ' ')
|
|
96
|
+
.trim();
|
|
97
|
+
}
|