-
Notifications
You must be signed in to change notification settings - Fork 152
Expand file tree
/
Copy pathcli-import-boundaries.test.ts
More file actions
101 lines (86 loc) · 3.08 KB
/
cli-import-boundaries.test.ts
File metadata and controls
101 lines (86 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { describe, expect, test } from 'bun:test';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path';
type ImportViolation = {
filePath: string;
specifier: string;
reason: string;
};
const CLI_SRC_ROOT = new URL('../../', import.meta.url);
const CLI_SRC_ROOT_PATH = CLI_SRC_ROOT.pathname;
const BANNED_IMPORT_PATTERNS: ReadonlyArray<{ pattern: RegExp; reason: string }> = [
{
pattern: /document-api-adapters\//,
reason: 'CLI modules must not import document-api-adapters internals directly.',
},
{
pattern: /(?:^|\/)super-editor\/src\//,
reason: 'CLI modules must not import super-editor source internals directly.',
},
{
pattern: /(?:^|\/)layout-engine\/(?:layout-engine|painters|style-engine)\//,
reason: 'CLI modules must not import layout-engine internals directly.',
},
{
pattern: /(?:^|\/)prosemirror(?:-|\/)/,
reason: 'CLI modules must not depend on ProseMirror internals directly.',
},
];
function listTypeScriptFiles(rootPath: string): string[] {
const files: string[] = [];
function walk(currentPath: string): void {
const entries = readdirSync(currentPath);
for (const entry of entries) {
const absolutePath = join(currentPath, entry);
const info = statSync(absolutePath);
if (info.isDirectory()) {
walk(absolutePath);
continue;
}
if (!entry.endsWith('.ts')) continue;
if (absolutePath.includes('/__tests__/')) continue;
files.push(absolutePath);
}
}
walk(rootPath);
return files;
}
function extractImportSpecifiers(fileContents: string): string[] {
const importSpecifiers: string[] = [];
const staticImportPattern = /import\s+(?:type\s+)?[\s\S]*?\sfrom\s+['"]([^'"]+)['"]/g;
const sideEffectImportPattern = /import\s+['"]([^'"]+)['"]/g;
const dynamicImportPattern = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
for (const pattern of [staticImportPattern, sideEffectImportPattern, dynamicImportPattern]) {
for (const match of fileContents.matchAll(pattern)) {
const specifier = match[1];
if (specifier) importSpecifiers.push(specifier);
}
}
return importSpecifiers;
}
function findImportViolations(): ImportViolation[] {
const files = listTypeScriptFiles(CLI_SRC_ROOT_PATH);
const violations: ImportViolation[] = [];
for (const filePath of files) {
const contents = readFileSync(filePath, 'utf8');
const specifiers = extractImportSpecifiers(contents);
for (const specifier of specifiers) {
for (const { pattern, reason } of BANNED_IMPORT_PATTERNS) {
if (!pattern.test(specifier)) continue;
violations.push({ filePath, specifier, reason });
}
}
}
return violations;
}
describe('cli import boundaries', () => {
test('prevents adapter and engine-internal imports outside bridge modules', () => {
const violations = findImportViolations();
const details = violations.map((entry) => ({
filePath: entry.filePath.replace(CLI_SRC_ROOT_PATH, 'src/'),
specifier: entry.specifier,
reason: entry.reason,
}));
expect(details).toEqual([]);
});
});