Skip to content

Commit b0bb390

Browse files
[WIP] Grids: create auto doc script (DevExpress#32706)
1 parent 3590d6e commit b0bb390

10 files changed

Lines changed: 2166 additions & 0 deletions

File tree

packages/devextreme/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ node_modules
77
/build/bundle-templates/dx.custom.js
88
/js/__internal/core/localization/default_messages.ts
99
/js/__internal/core/localization/cldr-data
10+
/js/__internal/grids/__docs__/artifacts
1011
/scss/bundles/*.scss
1112
RawLog.txt
1213
testing/CompletedSuites.txt
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
interface CliArgs {
2+
jsonOnly: boolean;
3+
htmlOnly: boolean;
4+
}
5+
6+
export function parseArgs(): CliArgs {
7+
const args = process.argv.slice(2);
8+
const result: CliArgs = {
9+
jsonOnly: false,
10+
htmlOnly: false,
11+
};
12+
13+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
14+
for (let i = 0; i < args.length; i += 1) {
15+
switch (args[i]) {
16+
case '--json':
17+
result.jsonOnly = true;
18+
break;
19+
case '--html':
20+
result.htmlOnly = true;
21+
break;
22+
default:
23+
console.error(`Error: Unknown argument "${args[i]}"`);
24+
process.exit(1);
25+
}
26+
}
27+
28+
if (result.jsonOnly && result.htmlOnly) {
29+
console.error('Error: Cannot specify both --json and --html. Use neither to generate both.');
30+
process.exit(1);
31+
}
32+
33+
return result;
34+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as path from 'path';
2+
3+
export const GRID_CORE_ROOT = path.resolve(__dirname, '..', '..', 'grid_core');
4+
export const OUTPUT_DIR = path.resolve(__dirname, '..', 'artifacts');
5+
6+
export const MODULE_SUFFIX = 'Module';
7+
export const MODULES_PREFIX = 'modules.';
8+
export const MODULE_ITEM_CLASS = 'ModuleItem';
9+
export const M_MODULES_PATH = 'm_modules';
10+
export const BARE_MODULE_BASES = ['Controller', 'View', 'ViewController'];
11+
12+
export const EXCLUDED_DIRS = new Set(['__tests__', 'scripts', 'new', '__docs__']);
13+
export const EXCLUDED_FILE_NAMES = new Set(['m_modules.ts']);
14+
15+
const CORE_DIRECTORY_FEATURE_MAP: Record<string, string> = {
16+
data_controller: 'Data',
17+
18+
views: 'Core',
19+
editor_factory: 'Core',
20+
error_handling: 'Core',
21+
22+
editing: 'Editing',
23+
validating: 'Editing',
24+
25+
selection: 'Selection',
26+
27+
filter: 'Filtering',
28+
header_filter: 'Filtering',
29+
search: 'Filtering',
30+
31+
keyboard_navigation: 'Navigation',
32+
focus: 'Navigation',
33+
34+
columns_controller: 'Columns Core',
35+
column_headers: 'Columns Core',
36+
header_panel: 'Columns Core',
37+
38+
column_chooser: 'Column Management',
39+
column_fixing: 'Column Management',
40+
sticky_columns: 'Column Management',
41+
virtual_columns: 'Column Management',
42+
columns_resizing_reordering: 'Column Management',
43+
adaptivity: 'Column Management',
44+
45+
virtual_scrolling: 'Scrolling',
46+
47+
ai_column: 'AI',
48+
ai_prompt_editor: 'AI',
49+
};
50+
51+
/**
52+
* Derive the feature area from a file path relative to grid_core root.
53+
* Uses the first directory segment to look up the feature, defaulting to 'Other'.
54+
*/
55+
export function getFeatureAreaFromPath(relPath: string): string {
56+
const firstSegment = relPath.split('/')[0];
57+
return CORE_DIRECTORY_FEATURE_MAP[firstSegment] ?? 'Other';
58+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env tsx
2+
/* eslint-disable no-console, no-restricted-syntax */
3+
import * as fs from 'fs';
4+
import * as path from 'path';
5+
6+
import { parseArgs } from './cli';
7+
import { GRID_CORE_ROOT, OUTPUT_DIR } from './constants';
8+
import { generateHtml } from './html-template';
9+
import { discoverSourceFiles, getRelativePath, parseFile } from './parser';
10+
import {
11+
buildGlobalAliasMap,
12+
buildGlobalClassRegistry,
13+
buildInheritanceChains,
14+
findStandaloneRegistrations,
15+
resolveAliasesInClasses,
16+
resolveModuleClassRefs,
17+
resolveRuntimeDeps,
18+
validateData,
19+
} from './resolver';
20+
import type { ArchitectureData, ModuleInfo } from './types';
21+
22+
function countItems(modules: ModuleInfo[], key: 'controllers' | 'views'): number {
23+
return modules.reduce((sum, m) => sum + Object.keys(m[key]).length, 0);
24+
}
25+
26+
function main(): void {
27+
console.log('Grid Core Architecture Documentation Generator');
28+
console.log(`Source root: ${GRID_CORE_ROOT}`);
29+
console.log(`Output dir: ${OUTPUT_DIR}`);
30+
31+
try {
32+
// 1. Discover source files
33+
const sourceFiles = discoverSourceFiles(GRID_CORE_ROOT);
34+
console.log(`Discovered ${sourceFiles.length} source files`);
35+
36+
// 2. Parse all files
37+
const allParsedFiles = sourceFiles.flatMap((file) => {
38+
console.log(`Parsing: ${getRelativePath(file)}`);
39+
try {
40+
return [parseFile(file)];
41+
} catch (e) {
42+
const errorMessage = e instanceof Error ? e.message : String(e);
43+
console.warn(`WARN: Failed to parse ${getRelativePath(file)}: ${errorMessage}`);
44+
45+
return [];
46+
}
47+
});
48+
49+
// 2a. Build global class registry
50+
const globalClasses = buildGlobalClassRegistry(allParsedFiles);
51+
console.log(`Global class registry: ${globalClasses.size} classes`);
52+
53+
// 2b. Build global import alias map
54+
const globalAliasMap = buildGlobalAliasMap(allParsedFiles);
55+
console.log(`Global alias map: ${globalAliasMap.size} aliases`);
56+
57+
// 2c. Resolve aliases
58+
resolveAliasesInClasses(globalClasses, globalAliasMap);
59+
60+
// 3. Collect modules and re-resolve cross-file class references
61+
const modules: ModuleInfo[] = [];
62+
for (const pf of allParsedFiles) {
63+
for (const mod of pf.modules) {
64+
resolveModuleClassRefs(mod, pf, globalClasses, allParsedFiles, globalAliasMap);
65+
modules.push(mod);
66+
}
67+
}
68+
modules.sort((a, b) => a.moduleName.localeCompare(b.moduleName));
69+
console.log(`Found ${modules.length} modules`);
70+
71+
// 4. Find standalone controllers and views (not registered in any module)
72+
const {
73+
controllers: standaloneControllers,
74+
views: standaloneViews,
75+
} = findStandaloneRegistrations(modules, globalClasses);
76+
console.log(`Found ${Object.keys(standaloneControllers).length} standalone controllers, ${Object.keys(standaloneViews).length} standalone views`);
77+
78+
// 5. Build inheritance chains
79+
const inheritanceChains = buildInheritanceChains(
80+
modules,
81+
standaloneControllers,
82+
standaloneViews,
83+
globalClasses,
84+
);
85+
console.log(`Built ${inheritanceChains.length} inheritance chains`);
86+
87+
// 6. Resolve runtime dependencies
88+
const runtimeDependencies = resolveRuntimeDeps(allParsedFiles, modules);
89+
console.log(`Found ${runtimeDependencies.length} runtime dependencies`);
90+
91+
// 7. Validate
92+
validateData(modules, standaloneControllers, standaloneViews, runtimeDependencies);
93+
94+
// 8. Build output data
95+
const data: ArchitectureData = {
96+
generatedAt: new Date().toISOString(),
97+
sourceRoot: 'packages/devextreme/js/__internal/grids/grid_core',
98+
modules,
99+
standaloneControllers,
100+
standaloneViews,
101+
runtimeDependencies,
102+
inheritanceChains,
103+
};
104+
105+
// 9. Write output files
106+
if (!fs.existsSync(OUTPUT_DIR)) {
107+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
108+
}
109+
110+
const args = parseArgs();
111+
if (!args.htmlOnly) {
112+
const jsonPath = path.join(OUTPUT_DIR, 'grid_core_architecture.generated.json');
113+
fs.writeFileSync(jsonPath, `${JSON.stringify(data, null, 2)}\n`);
114+
console.log(`✓ JSON written to: ${jsonPath}`);
115+
}
116+
117+
if (!args.jsonOnly) {
118+
const htmlPath = path.join(OUTPUT_DIR, 'grid_core_architecture.generated.html');
119+
fs.writeFileSync(htmlPath, generateHtml(data));
120+
console.log(`✓ HTML written to: ${htmlPath}`);
121+
}
122+
123+
console.log('\nSummary:');
124+
console.log(` Modules: ${modules.length}`);
125+
console.log(` Controllers: ${countItems(modules, 'controllers')}`);
126+
console.log(` Views: ${countItems(modules, 'views')}`);
127+
console.log(` Extension-only modules: ${modules.filter((m) => Object.keys(m.controllers).length === 0 && Object.keys(m.views).length === 0).length}`);
128+
console.log(` Standalone controllers: ${Object.keys(standaloneControllers).length}`);
129+
console.log(` Standalone views: ${Object.keys(standaloneViews).length}`);
130+
console.log(` Runtime dependencies: ${runtimeDependencies.length}`);
131+
console.log(` Inheritance chains: ${inheritanceChains.length}`);
132+
} catch (e) {
133+
const errorMessage = e instanceof Error ? e.message : String(e);
134+
console.error(`ERROR: ${errorMessage}`);
135+
process.exit(1);
136+
}
137+
}
138+
139+
main();

0 commit comments

Comments
 (0)