@stag-build/phonebook 0.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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +33 -0
- package/dist/commands/doctor.js +690 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +27 -0
- package/dist/commands/init.js +415 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -0
- package/dist/engines/android.d.ts +70 -0
- package/dist/engines/android.js +265 -0
- package/dist/engines/android.js.map +1 -0
- package/dist/engines/git.d.ts +4 -0
- package/dist/engines/git.js +13 -0
- package/dist/engines/git.js.map +1 -0
- package/dist/engines/ios.d.ts +55 -0
- package/dist/engines/ios.js +195 -0
- package/dist/engines/ios.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/gradle/catalog.d.ts +110 -0
- package/dist/gradle/catalog.js +413 -0
- package/dist/gradle/catalog.js.map +1 -0
- package/dist/ios/snapshotTestClass.d.ts +59 -0
- package/dist/ios/snapshotTestClass.js +195 -0
- package/dist/ios/snapshotTestClass.js.map +1 -0
- package/dist/manifest.d.ts +32 -0
- package/dist/manifest.js +23 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +226 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/naming.d.ts +18 -0
- package/dist/naming.js +34 -0
- package/dist/naming.js.map +1 -0
- package/dist/scan/android.d.ts +7 -0
- package/dist/scan/android.js +222 -0
- package/dist/scan/android.js.map +1 -0
- package/dist/scan/hints.d.ts +25 -0
- package/dist/scan/hints.js +338 -0
- package/dist/scan/hints.js.map +1 -0
- package/dist/scan/ios.d.ts +7 -0
- package/dist/scan/ios.js +201 -0
- package/dist/scan/ios.js.map +1 -0
- package/dist/scan/types.d.ts +52 -0
- package/dist/scan/types.js +7 -0
- package/dist/scan/types.js.map +1 -0
- package/dist/site/build.d.ts +17 -0
- package/dist/site/build.js +0 -0
- package/dist/site/build.js.map +1 -0
- package/dist/versions.d.ts +71 -0
- package/dist/versions.js +334 -0
- package/dist/versions.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { previewHints } from './hints.js';
|
|
4
|
+
/**
|
|
5
|
+
* Regex-based heuristic scanner for Jetpack Compose `@Composable` / `@Preview`
|
|
6
|
+
* functions. Walks each module's `src/main` tree and matches previews to
|
|
7
|
+
* components by file + name-prefix.
|
|
8
|
+
*/
|
|
9
|
+
export async function scanAndroid(projectDir, modules) {
|
|
10
|
+
const components = [];
|
|
11
|
+
const orphanPreviews = [];
|
|
12
|
+
for (const module of modules) {
|
|
13
|
+
const moduleDir = join(projectDir, ...module.split(':').filter(Boolean));
|
|
14
|
+
const srcRoot = join(moduleDir, 'src', 'main');
|
|
15
|
+
const files = await walkKotlinFiles(srcRoot);
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const relFile = relative(projectDir, file);
|
|
18
|
+
const content = await readFile(file, 'utf8');
|
|
19
|
+
const { fileComponents, filePreviews } = scanKotlinFile(content, relFile);
|
|
20
|
+
for (const comp of fileComponents)
|
|
21
|
+
components.push(comp);
|
|
22
|
+
matchPreviewsToComponents(filePreviews, fileComponents, orphanPreviews);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
components.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
26
|
+
orphanPreviews.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
27
|
+
return {
|
|
28
|
+
platform: 'android',
|
|
29
|
+
components,
|
|
30
|
+
orphanPreviews,
|
|
31
|
+
stats: computeStats(components, orphanPreviews),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function scanKotlinFile(content, relFile) {
|
|
35
|
+
const functions = findAnnotatedFunctions(content);
|
|
36
|
+
const fileComponents = [];
|
|
37
|
+
const filePreviews = [];
|
|
38
|
+
const previewComposables = [];
|
|
39
|
+
for (const fn of functions) {
|
|
40
|
+
const isComposable = /@Composable\b/.test(fn.annotations);
|
|
41
|
+
if (!isComposable)
|
|
42
|
+
continue;
|
|
43
|
+
const previewMatches = [...fn.annotations.matchAll(/@Preview\b(\([^)]*\))?/g)];
|
|
44
|
+
if (previewMatches.length > 0) {
|
|
45
|
+
// @Preview @Composable: either a wrapper preview of a separate component, or
|
|
46
|
+
// a "self-preview" (a screen-level composable that previews itself via
|
|
47
|
+
// default parameters). Decided below once all non-preview components in the
|
|
48
|
+
// file are known.
|
|
49
|
+
let displayName;
|
|
50
|
+
let dark = false;
|
|
51
|
+
for (const m of previewMatches) {
|
|
52
|
+
const args = m[1] ?? '';
|
|
53
|
+
const nameMatch = args.match(/name\s*=\s*"([^"]*)"/);
|
|
54
|
+
if (nameMatch && !displayName)
|
|
55
|
+
displayName = nameMatch[1];
|
|
56
|
+
if (/UI_MODE_NIGHT_YES/.test(args))
|
|
57
|
+
dark = true;
|
|
58
|
+
}
|
|
59
|
+
if (/(Dark|Night)$/.test(fn.name))
|
|
60
|
+
dark = true;
|
|
61
|
+
const annotationText = previewMatches.map((m) => m[0]).join('\n');
|
|
62
|
+
const bodyText = captureBodyText(content, fn.funIndex);
|
|
63
|
+
const hints = previewHints({
|
|
64
|
+
platform: 'android',
|
|
65
|
+
functionName: fn.name,
|
|
66
|
+
displayName,
|
|
67
|
+
annotationText,
|
|
68
|
+
bodyText,
|
|
69
|
+
});
|
|
70
|
+
previewComposables.push({
|
|
71
|
+
fn,
|
|
72
|
+
preview: {
|
|
73
|
+
name: fn.name,
|
|
74
|
+
...(displayName ? { displayName } : {}),
|
|
75
|
+
file: relFile,
|
|
76
|
+
line: fn.line,
|
|
77
|
+
dark,
|
|
78
|
+
annotationText,
|
|
79
|
+
...(bodyText ? { bodyText } : {}),
|
|
80
|
+
...(hints.length > 0 ? { hints } : {}),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
fileComponents.push({
|
|
86
|
+
name: fn.name,
|
|
87
|
+
file: relFile,
|
|
88
|
+
line: fn.line,
|
|
89
|
+
previews: [],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Classify each @Preview-annotated composable: if its name matches an existing
|
|
94
|
+
// non-preview component in this file by the prefix heuristic, it's a wrapper
|
|
95
|
+
// preview of that component (existing behavior). Otherwise it's a self-preview:
|
|
96
|
+
// the function is both a component and its own preview.
|
|
97
|
+
for (const { fn, preview } of previewComposables) {
|
|
98
|
+
const wrapperTarget = bestPrefixMatch(fn.name, fileComponents);
|
|
99
|
+
if (wrapperTarget) {
|
|
100
|
+
filePreviews.push(preview);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
fileComponents.push({
|
|
104
|
+
name: fn.name,
|
|
105
|
+
file: relFile,
|
|
106
|
+
line: fn.line,
|
|
107
|
+
previews: [preview],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { fileComponents, filePreviews };
|
|
112
|
+
}
|
|
113
|
+
/** Longest-name-prefix match, same heuristic as `matchPreviewsToComponents`. */
|
|
114
|
+
function bestPrefixMatch(name, components) {
|
|
115
|
+
let best;
|
|
116
|
+
for (const comp of components) {
|
|
117
|
+
if (name.startsWith(comp.name)) {
|
|
118
|
+
if (!best || comp.name.length > best.name.length)
|
|
119
|
+
best = comp;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return best;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Finds `fun Name(` declarations preceded by uppercase-first names, along with
|
|
126
|
+
* the block of annotations (e.g. `@Composable`, `@Preview(...)`) found within
|
|
127
|
+
* roughly 5 lines above the `fun` keyword.
|
|
128
|
+
*/
|
|
129
|
+
function findAnnotatedFunctions(content) {
|
|
130
|
+
const results = [];
|
|
131
|
+
const funRegex = /\bfun\s+([A-Z]\w*)\s*\(/g;
|
|
132
|
+
let match;
|
|
133
|
+
while ((match = funRegex.exec(content)) !== null) {
|
|
134
|
+
const name = match[1];
|
|
135
|
+
const funIndex = match.index;
|
|
136
|
+
const line = lineNumberAt(content, funIndex);
|
|
137
|
+
// Look back up to 5 lines above the `fun` line for annotations.
|
|
138
|
+
const linesBefore = content.slice(0, funIndex).split('\n');
|
|
139
|
+
const contextLines = linesBefore.slice(Math.max(0, linesBefore.length - 6));
|
|
140
|
+
const annotations = contextLines.join('\n');
|
|
141
|
+
results.push({ name, line, annotations, funIndex });
|
|
142
|
+
}
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
function lineNumberAt(content, index) {
|
|
146
|
+
let line = 1;
|
|
147
|
+
for (let i = 0; i < index; i++) {
|
|
148
|
+
if (content[i] === '\n')
|
|
149
|
+
line++;
|
|
150
|
+
}
|
|
151
|
+
return line;
|
|
152
|
+
}
|
|
153
|
+
const BODY_LINE_CAP = 20;
|
|
154
|
+
/** Captures the preview function's body from its opening brace, capped to ~20 lines.
|
|
155
|
+
* A simple line cap (no brace matching) -- good enough for hints to pattern-match against. */
|
|
156
|
+
function captureBodyText(content, funIndex) {
|
|
157
|
+
const braceIndex = content.indexOf('{', funIndex);
|
|
158
|
+
if (braceIndex === -1)
|
|
159
|
+
return undefined;
|
|
160
|
+
const lines = content.slice(braceIndex).split('\n').slice(0, BODY_LINE_CAP);
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Matches previews to components using: same file + preview name starts with
|
|
165
|
+
* component name (longest component-name match wins). Unmatched previews are
|
|
166
|
+
* pushed to `orphanPreviews`.
|
|
167
|
+
*/
|
|
168
|
+
function matchPreviewsToComponents(previews, components, orphanPreviews) {
|
|
169
|
+
for (const preview of previews) {
|
|
170
|
+
let best;
|
|
171
|
+
for (const comp of components) {
|
|
172
|
+
if (preview.name.startsWith(comp.name)) {
|
|
173
|
+
if (!best || comp.name.length > best.name.length)
|
|
174
|
+
best = comp;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (best) {
|
|
178
|
+
best.previews.push(preview);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
orphanPreviews.push(preview);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function computeStats(components, orphanPreviews) {
|
|
186
|
+
const withPreview = components.filter((c) => c.previews.length > 0).length;
|
|
187
|
+
const withDarkPreview = components.filter((c) => c.previews.some((p) => p.dark)).length;
|
|
188
|
+
const totalPreviews = components.reduce((sum, c) => sum + c.previews.length, 0) + orphanPreviews.length;
|
|
189
|
+
const allPreviews = [...components.flatMap((c) => c.previews), ...orphanPreviews];
|
|
190
|
+
const hintCount = allPreviews.reduce((sum, p) => sum + (p.hints?.length ?? 0), 0);
|
|
191
|
+
const selfPreviewed = components.filter((c) => c.previews.some((p) => p.name === c.name && p.line === c.line)).length;
|
|
192
|
+
return {
|
|
193
|
+
components: components.length,
|
|
194
|
+
withPreview,
|
|
195
|
+
withDarkPreview,
|
|
196
|
+
totalPreviews,
|
|
197
|
+
hintCount,
|
|
198
|
+
selfPreviewed,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function walkKotlinFiles(dir) {
|
|
202
|
+
const results = [];
|
|
203
|
+
let entries;
|
|
204
|
+
try {
|
|
205
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return results;
|
|
209
|
+
}
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (entry.isDirectory()) {
|
|
212
|
+
if (entry.name === 'build')
|
|
213
|
+
continue;
|
|
214
|
+
results.push(...(await walkKotlinFiles(join(dir, entry.name))));
|
|
215
|
+
}
|
|
216
|
+
else if (entry.isFile() && entry.name.endsWith('.kt')) {
|
|
217
|
+
results.push(join(dir, entry.name));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=android.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"android.js","sourceRoot":"","sources":["../../src/scan/android.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview configuration hints: heuristics that catch a preview whose *name*
|
|
3
|
+
* (composable/View function name, or its `@Preview`/`#Preview` display name)
|
|
4
|
+
* implies a configuration — landscape, dark theme, tablet, RTL, etc. — that
|
|
5
|
+
* the annotation never actually declares. Real finding that motivated this:
|
|
6
|
+
* `fun SplashContentLandscape()` annotated with plain
|
|
7
|
+
* `@Preview(showBackground = true)` rendered portrait, so the gallery showed
|
|
8
|
+
* a screenshot named "Landscape" that wasn't.
|
|
9
|
+
*/
|
|
10
|
+
export interface PreviewHint {
|
|
11
|
+
rule: string;
|
|
12
|
+
severity: 'warning' | 'info';
|
|
13
|
+
message: string;
|
|
14
|
+
suggestion: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PreviewHintInput {
|
|
17
|
+
platform: 'android' | 'ios';
|
|
18
|
+
functionName: string;
|
|
19
|
+
displayName?: string;
|
|
20
|
+
annotationText: string;
|
|
21
|
+
/** Text of the preview function's body (from the opening brace, capped to ~20 lines).
|
|
22
|
+
* Android-only for now; used by the `size-in-body` rule. */
|
|
23
|
+
bodyText?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function previewHints(input: PreviewHintInput): PreviewHint[];
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview configuration hints: heuristics that catch a preview whose *name*
|
|
3
|
+
* (composable/View function name, or its `@Preview`/`#Preview` display name)
|
|
4
|
+
* implies a configuration — landscape, dark theme, tablet, RTL, etc. — that
|
|
5
|
+
* the annotation never actually declares. Real finding that motivated this:
|
|
6
|
+
* `fun SplashContentLandscape()` annotated with plain
|
|
7
|
+
* `@Preview(showBackground = true)` rendered portrait, so the gallery showed
|
|
8
|
+
* a screenshot named "Landscape" that wasn't.
|
|
9
|
+
*/
|
|
10
|
+
import { spaceCamelCase } from '../naming.js';
|
|
11
|
+
/** Extracts width/height (in dp) from a `device = "spec:width=..dp,height=..dp,..."` value,
|
|
12
|
+
* or from standalone `widthDp = N` / `heightDp = N` params. */
|
|
13
|
+
function extractDims(text) {
|
|
14
|
+
const deviceMatch = text.match(/device\s*=\s*"([^"]*)"/);
|
|
15
|
+
const scope = deviceMatch ? deviceMatch[1] : '';
|
|
16
|
+
const widthMatch = scope.match(/width\s*=\s*(\d+)dp/i) ?? text.match(/widthDp\s*=\s*(\d+)/i);
|
|
17
|
+
const heightMatch = scope.match(/height\s*=\s*(\d+)dp/i) ?? text.match(/heightDp\s*=\s*(\d+)/i);
|
|
18
|
+
return {
|
|
19
|
+
width: widthMatch ? Number(widthMatch[1]) : undefined,
|
|
20
|
+
height: heightMatch ? Number(heightMatch[1]) : undefined,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function androidDeclaresLandscape(text) {
|
|
24
|
+
if (/orientation\s*=\s*landscape/i.test(text))
|
|
25
|
+
return true;
|
|
26
|
+
const { width, height } = extractDims(text);
|
|
27
|
+
return width !== undefined && height !== undefined && width > height;
|
|
28
|
+
}
|
|
29
|
+
const ANDROID_RULES = [
|
|
30
|
+
{
|
|
31
|
+
rule: 'orientation-landscape',
|
|
32
|
+
severity: 'warning',
|
|
33
|
+
namePattern: /landscape/i,
|
|
34
|
+
declared: (ctx) => androidDeclaresLandscape(ctx.annotationText),
|
|
35
|
+
message: (ctx) => `"${ctx.matchedName}" implies landscape orientation, but the @Preview annotation doesn't declare a landscape device/orientation.`,
|
|
36
|
+
suggestion: () => 'on the @Preview annotation: device = "spec:width=891dp,height=411dp,orientation=landscape"',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rule: 'orientation-portrait',
|
|
40
|
+
severity: 'warning',
|
|
41
|
+
namePattern: /portrait/i,
|
|
42
|
+
declared: (ctx) => !androidDeclaresLandscape(ctx.annotationText),
|
|
43
|
+
message: (ctx) => `"${ctx.matchedName}" implies portrait orientation, but the @Preview annotation declares a landscape device.`,
|
|
44
|
+
suggestion: () => 'on the @Preview annotation: device = "spec:width=411dp,height=891dp,orientation=portrait"',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
rule: 'theme-dark',
|
|
48
|
+
severity: 'warning',
|
|
49
|
+
namePattern: /\b(dark|night)\b/i,
|
|
50
|
+
declared: (ctx) => /UI_MODE_NIGHT_YES/.test(ctx.annotationText),
|
|
51
|
+
message: (ctx) => `"${ctx.matchedName}" implies dark theme, but the @Preview annotation doesn't set UI_MODE_NIGHT_YES.`,
|
|
52
|
+
suggestion: () => 'uiMode = Configuration.UI_MODE_NIGHT_YES (import android.content.res.Configuration)',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
rule: 'theme-light',
|
|
56
|
+
severity: 'warning',
|
|
57
|
+
namePattern: /\blight\b/i,
|
|
58
|
+
declared: (ctx) => !/UI_MODE_NIGHT_YES/.test(ctx.annotationText),
|
|
59
|
+
message: (ctx) => `"${ctx.matchedName}" implies light theme, but the @Preview annotation sets UI_MODE_NIGHT_YES.`,
|
|
60
|
+
suggestion: () => 'remove uiMode, or set uiMode = Configuration.UI_MODE_NIGHT_NO',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
rule: 'device-tablet',
|
|
64
|
+
severity: 'warning',
|
|
65
|
+
namePattern: /\b(tablet|foldable|expanded|large ?screen)\b/i,
|
|
66
|
+
declared: (ctx) => /device\s*=/.test(ctx.annotationText),
|
|
67
|
+
message: (ctx) => `"${ctx.matchedName}" implies a tablet/foldable/expanded device, but the @Preview annotation doesn't set device.`,
|
|
68
|
+
suggestion: (ctx) => /foldable/i.test(ctx.matchedName)
|
|
69
|
+
? 'on the @Preview annotation: device = "spec:width=673dp,height=841dp" (unfolded foldable spec)'
|
|
70
|
+
: 'on the @Preview annotation: device = Devices.TABLET',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
rule: 'locale-rtl',
|
|
74
|
+
severity: 'warning',
|
|
75
|
+
namePattern: /\b(rtl|arabic|hebrew)\b/i,
|
|
76
|
+
declared: (ctx) => /locale\s*=/.test(ctx.annotationText),
|
|
77
|
+
message: (ctx) => `"${ctx.matchedName}" implies an RTL locale, but the @Preview annotation doesn't set locale.`,
|
|
78
|
+
suggestion: (ctx) => (/hebrew/i.test(ctx.matchedName) ? 'locale = "he"' : 'locale = "ar"'),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
rule: 'font-scale',
|
|
82
|
+
severity: 'warning',
|
|
83
|
+
namePattern: /\b(large ?font|font ?scale|accessibility|a11y)\b/i,
|
|
84
|
+
declared: (ctx) => /fontScale\s*=/.test(ctx.annotationText),
|
|
85
|
+
message: (ctx) => `"${ctx.matchedName}" implies a large font scale, but the @Preview annotation doesn't set fontScale.`,
|
|
86
|
+
suggestion: () => 'fontScale = 1.5f',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
rule: 'size-compact',
|
|
90
|
+
severity: 'warning',
|
|
91
|
+
namePattern: /\b(compact|small ?screen|phone ?small)\b/i,
|
|
92
|
+
declared: (ctx) => /widthDp\s*=/.test(ctx.annotationText) || /device\s*=/.test(ctx.annotationText),
|
|
93
|
+
message: (ctx) => `"${ctx.matchedName}" implies a compact/small-screen size, but the @Preview annotation doesn't set widthDp or device.`,
|
|
94
|
+
suggestion: () => 'on the @Preview annotation: widthDp = 320',
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
/** Matches a fixed-size Modifier call (e.g. `.size(width = 891.dp, height = 411.dp)`) with
|
|
98
|
+
* a simple, non-nested argument list — the shape these calls take in practice. A call whose
|
|
99
|
+
* argument contains its own parens (e.g. `.size(dimensionResource(R.dimen.x))`) deliberately
|
|
100
|
+
* doesn't match: we have no literal dp value to reason about, so there's nothing to report. */
|
|
101
|
+
const SIZE_MODIFIER_RE = /\.(size|requiredSize|width|height|requiredWidth|requiredHeight)\s*\(([^()]*)\)/g;
|
|
102
|
+
/** Scans (a segment of) a preview body for fixed-size Modifier calls and, where parseable, the
|
|
103
|
+
* dp values they set. Handles `.size(48.dp)`, `.size(300.dp, 200.dp)`,
|
|
104
|
+
* `.size(width = 891.dp, height = 411.dp)`, and separate `.width(...)`/`.height(...)` calls. */
|
|
105
|
+
function parseBodySize(text) {
|
|
106
|
+
const modifiers = [];
|
|
107
|
+
let width;
|
|
108
|
+
let height;
|
|
109
|
+
for (const match of text.matchAll(SIZE_MODIFIER_RE)) {
|
|
110
|
+
const mod = match[1];
|
|
111
|
+
const args = match[2];
|
|
112
|
+
modifiers.push(mod);
|
|
113
|
+
const widthArg = args.match(/width\s*=\s*(\d+(?:\.\d+)?)\.dp/i);
|
|
114
|
+
const heightArg = args.match(/height\s*=\s*(\d+(?:\.\d+)?)\.dp/i);
|
|
115
|
+
const dpValues = [...args.matchAll(/(\d+(?:\.\d+)?)\.dp/g)].map((m) => Number(m[1]));
|
|
116
|
+
if (mod === 'size' || mod === 'requiredSize') {
|
|
117
|
+
if (widthArg)
|
|
118
|
+
width = Number(widthArg[1]);
|
|
119
|
+
if (heightArg)
|
|
120
|
+
height = Number(heightArg[1]);
|
|
121
|
+
if (!widthArg && !heightArg) {
|
|
122
|
+
if (dpValues.length === 1) {
|
|
123
|
+
width = dpValues[0];
|
|
124
|
+
height = dpValues[0];
|
|
125
|
+
}
|
|
126
|
+
else if (dpValues.length >= 2) {
|
|
127
|
+
width = dpValues[0];
|
|
128
|
+
height = dpValues[1];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (mod === 'width' || mod === 'requiredWidth') {
|
|
133
|
+
if (dpValues.length >= 1)
|
|
134
|
+
width = dpValues[0];
|
|
135
|
+
}
|
|
136
|
+
else if (mod === 'height' || mod === 'requiredHeight') {
|
|
137
|
+
if (dpValues.length >= 1)
|
|
138
|
+
height = dpValues[0];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (modifiers.length === 0)
|
|
142
|
+
return undefined;
|
|
143
|
+
return { modifiers, width, height };
|
|
144
|
+
}
|
|
145
|
+
function androidDeclaresExplicitSize(annotationText) {
|
|
146
|
+
return (/widthDp\s*=/.test(annotationText) ||
|
|
147
|
+
/heightDp\s*=/.test(annotationText) ||
|
|
148
|
+
/device\s*=/.test(annotationText));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The default Compose preview canvas (a Pixel-ish phone) when no widthDp/heightDp/device
|
|
152
|
+
* is declared: 392dp wide, 892dp tall. A size set on the root composable only causes visible
|
|
153
|
+
* harm (clipping) when it exceeds these — an icon's `.size(40.dp)` is completely harmless.
|
|
154
|
+
*/
|
|
155
|
+
const DEFAULT_CANVAS_WIDTH_DP = 392;
|
|
156
|
+
const DEFAULT_CANVAS_HEIGHT_DP = 892;
|
|
157
|
+
/**
|
|
158
|
+
* Isolates the modifier chain of the preview body's ROOT composable call: the text from the
|
|
159
|
+
* start of the body up to the first `{` that opens that root call's content lambda. Nested
|
|
160
|
+
* composables (anything inside that lambda) are deliberately excluded — almost every preview
|
|
161
|
+
* body has *some* inner `.size(...)` (an icon, a row height, ...), and those never affect the
|
|
162
|
+
* canvas, so scanning the whole body would be all noise. A simple line/brace-position
|
|
163
|
+
* approximation, not a real parser: robust enough for the common `Root(modifier = ...) { ... }`
|
|
164
|
+
* shape, and for a bare `Root(...)` call with no trailing lambda (root segment == whole body).
|
|
165
|
+
*/
|
|
166
|
+
function rootComposableSegment(bodyText) {
|
|
167
|
+
// bodyText always starts with the function's own opening brace (see captureBodyText).
|
|
168
|
+
const searchStart = 1;
|
|
169
|
+
const lambdaOpen = bodyText.indexOf('{', searchStart);
|
|
170
|
+
return lambdaOpen === -1 ? bodyText.slice(searchStart) : bodyText.slice(searchStart, lambdaOpen);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* `size-in-body`: the preview body's ROOT composable sets a fixed size via a Modifier
|
|
174
|
+
* (`.size`/`.requiredSize`/`.width`/`.height`/`.requiredWidth`/`.requiredHeight`) that exceeds
|
|
175
|
+
* the default preview canvas (392dp wide / 892dp tall), while the annotation declares no canvas
|
|
176
|
+
* size of its own -- so the content is clipped instead of resized. This is a distinct mistake
|
|
177
|
+
* from `orientation-landscape`/`device-tablet`/`size-compact` (which key off the preview's
|
|
178
|
+
* *name*): here the body itself proves the mistake, regardless of naming. Both may fire together
|
|
179
|
+
* on the same preview -- a landscape-named preview that also over-sizes itself via Modifier is
|
|
180
|
+
* doubly wrong -- but their messages don't contradict: this rule only ever talks about the root
|
|
181
|
+
* Modifier size exceeding the canvas, never about orientation/name.
|
|
182
|
+
*
|
|
183
|
+
* Requires a literal, parseable dp value that actually exceeds the canvas: an in-bounds size
|
|
184
|
+
* (an icon's `.size(40.dp)`, a row's `.width(360.dp)`) or an unparseable expression (a
|
|
185
|
+
* dimension resource, a variable) never fires. No evidence, no warning.
|
|
186
|
+
*/
|
|
187
|
+
function checkSizeInBody(annotationText, bodyText) {
|
|
188
|
+
if (!bodyText)
|
|
189
|
+
return undefined;
|
|
190
|
+
if (androidDeclaresExplicitSize(annotationText))
|
|
191
|
+
return undefined;
|
|
192
|
+
const parsed = parseBodySize(rootComposableSegment(bodyText));
|
|
193
|
+
if (!parsed)
|
|
194
|
+
return undefined;
|
|
195
|
+
const { width, height } = parsed;
|
|
196
|
+
if (width === undefined && height === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
const widthExceeds = width !== undefined && width > DEFAULT_CANVAS_WIDTH_DP;
|
|
199
|
+
const heightExceeds = height !== undefined && height > DEFAULT_CANVAS_HEIGHT_DP;
|
|
200
|
+
if (!widthExceeds && !heightExceeds)
|
|
201
|
+
return undefined;
|
|
202
|
+
const reasons = [];
|
|
203
|
+
if (widthExceeds) {
|
|
204
|
+
reasons.push(`${width}dp of width, but the preview canvas is only ${DEFAULT_CANVAS_WIDTH_DP}dp wide`);
|
|
205
|
+
}
|
|
206
|
+
if (heightExceeds) {
|
|
207
|
+
reasons.push(`${height}dp of height, but the preview canvas is only ${DEFAULT_CANVAS_HEIGHT_DP}dp tall`);
|
|
208
|
+
}
|
|
209
|
+
const suggestedParts = [];
|
|
210
|
+
if (width !== undefined)
|
|
211
|
+
suggestedParts.push(`widthDp = ${width}`);
|
|
212
|
+
if (height !== undefined)
|
|
213
|
+
suggestedParts.push(`heightDp = ${height}`);
|
|
214
|
+
return {
|
|
215
|
+
rule: 'size-in-body',
|
|
216
|
+
severity: 'warning',
|
|
217
|
+
message: `The preview body's root composable requests ${reasons.join(' and ')}, so it is clipped ` +
|
|
218
|
+
`instead of resized. A size Modifier in the preview body sizes content inside the canvas -- ` +
|
|
219
|
+
`it does not resize the canvas itself.`,
|
|
220
|
+
suggestion: `on the @Preview annotation: @Preview(${suggestedParts.join(', ')})`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const IOS_RULES = [
|
|
224
|
+
{
|
|
225
|
+
rule: 'orientation-landscape',
|
|
226
|
+
severity: 'warning',
|
|
227
|
+
namePattern: /landscape/i,
|
|
228
|
+
declared: (ctx) => /\.landscapeLeft|\.landscapeRight|previewInterfaceOrientation/.test(ctx.annotationText),
|
|
229
|
+
message: (ctx) => `"${ctx.matchedName}" implies landscape orientation, but the #Preview doesn't declare .previewInterfaceOrientation(.landscape*).`,
|
|
230
|
+
suggestion: () => '.previewInterfaceOrientation(.landscapeLeft)',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
rule: 'theme-dark',
|
|
234
|
+
severity: 'warning',
|
|
235
|
+
namePattern: /\b(dark|night)\b/i,
|
|
236
|
+
declared: (ctx) => /\.preferredColorScheme\(\s*\.dark\s*\)/.test(ctx.annotationText),
|
|
237
|
+
message: (ctx) => `"${ctx.matchedName}" implies dark theme, but the #Preview doesn't declare .preferredColorScheme(.dark).`,
|
|
238
|
+
suggestion: () => '.preferredColorScheme(.dark)',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
rule: 'theme-light',
|
|
242
|
+
severity: 'warning',
|
|
243
|
+
namePattern: /\blight\b/i,
|
|
244
|
+
declared: (ctx) => !/\.preferredColorScheme\(\s*\.dark\s*\)/.test(ctx.annotationText),
|
|
245
|
+
message: (ctx) => `"${ctx.matchedName}" implies light theme, but the #Preview declares .preferredColorScheme(.dark).`,
|
|
246
|
+
suggestion: () => '.preferredColorScheme(.light)',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
rule: 'device-tablet',
|
|
250
|
+
severity: 'warning',
|
|
251
|
+
namePattern: /\b(tablet|ipad)\b/i,
|
|
252
|
+
declared: (ctx) => /\.previewDevice/.test(ctx.annotationText),
|
|
253
|
+
message: (ctx) => `"${ctx.matchedName}" implies an iPad/tablet device, but the #Preview doesn't declare .previewDevice.`,
|
|
254
|
+
suggestion: () => '.previewDevice("iPad Pro 13-inch (M4)")',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
rule: 'locale-rtl',
|
|
258
|
+
severity: 'warning',
|
|
259
|
+
namePattern: /\b(rtl|arabic|hebrew)\b/i,
|
|
260
|
+
declared: (ctx) => /layoutDirection/.test(ctx.annotationText),
|
|
261
|
+
message: (ctx) => `"${ctx.matchedName}" implies an RTL locale, but the #Preview doesn't set layoutDirection.`,
|
|
262
|
+
suggestion: () => '.environment(\\.layoutDirection, .rightToLeft)',
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
rule: 'font-scale',
|
|
266
|
+
severity: 'warning',
|
|
267
|
+
namePattern: /\b(large ?font|dynamic ?type|accessibility|a11y)\b/i,
|
|
268
|
+
declared: (ctx) => /dynamicTypeSize/.test(ctx.annotationText),
|
|
269
|
+
message: (ctx) => `"${ctx.matchedName}" implies a large/accessibility font size, but the #Preview doesn't set dynamicTypeSize.`,
|
|
270
|
+
suggestion: () => '.dynamicTypeSize(.accessibility3)',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
rule: 'sizing',
|
|
274
|
+
severity: 'info',
|
|
275
|
+
namePattern: /.*/,
|
|
276
|
+
declared: (ctx) => /traits\s*:/.test(ctx.annotationText),
|
|
277
|
+
skip: (ctx) => /(Screen|Page|Content)$/i.test(ctx.matchedName),
|
|
278
|
+
message: (ctx) => `"${ctx.matchedName}" doesn't declare a traits: value, so it renders at full device size instead of fit-to-content.`,
|
|
279
|
+
suggestion: () => 'traits: .sizeThatFitsLayout',
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
function nameCandidates(functionName, displayName) {
|
|
283
|
+
const candidates = [functionName];
|
|
284
|
+
if (displayName)
|
|
285
|
+
candidates.push(displayName);
|
|
286
|
+
return candidates;
|
|
287
|
+
}
|
|
288
|
+
function evalRules(rules, functionName, displayName, annotationText) {
|
|
289
|
+
const hints = [];
|
|
290
|
+
const candidates = nameCandidates(functionName, displayName);
|
|
291
|
+
for (const rule of rules) {
|
|
292
|
+
let matchedName;
|
|
293
|
+
for (const candidate of candidates) {
|
|
294
|
+
// Match against both the raw candidate (handles "/"-separated or
|
|
295
|
+
// already-spaced display names) and its camelCase-split form, so a
|
|
296
|
+
// word like "Landscape" is found inside "SplashContentLandscape" too.
|
|
297
|
+
if (rule.namePattern.test(candidate) || rule.namePattern.test(spaceCamelCase(candidate))) {
|
|
298
|
+
matchedName = candidate;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (matchedName === undefined)
|
|
303
|
+
continue;
|
|
304
|
+
const ctx = { functionName, displayName, annotationText, matchedName };
|
|
305
|
+
if (rule.skip?.(ctx))
|
|
306
|
+
continue;
|
|
307
|
+
if (rule.declared(ctx))
|
|
308
|
+
continue;
|
|
309
|
+
hints.push({
|
|
310
|
+
rule: rule.rule,
|
|
311
|
+
severity: rule.severity,
|
|
312
|
+
message: rule.message(ctx),
|
|
313
|
+
suggestion: rule.suggestion(ctx),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return hints;
|
|
317
|
+
}
|
|
318
|
+
export function previewHints(input) {
|
|
319
|
+
const { platform, functionName, displayName, annotationText, bodyText } = input;
|
|
320
|
+
const rules = platform === 'android' ? ANDROID_RULES : IOS_RULES;
|
|
321
|
+
const hints = evalRules(rules, functionName, displayName, annotationText);
|
|
322
|
+
if (platform === 'android') {
|
|
323
|
+
const sizeHint = checkSizeInBody(annotationText, bodyText);
|
|
324
|
+
if (sizeHint)
|
|
325
|
+
hints.push(sizeHint);
|
|
326
|
+
}
|
|
327
|
+
if (!displayName) {
|
|
328
|
+
const example = platform === 'android' ? '@Preview(name = "<Component>/<State>")' : '#Preview("<Component>/<State>")';
|
|
329
|
+
hints.push({
|
|
330
|
+
rule: 'unnamed-preview',
|
|
331
|
+
severity: 'info',
|
|
332
|
+
message: `"${functionName}" has no display name, so the gallery groups it under "Default" instead of a real state.`,
|
|
333
|
+
suggestion: `${example} per the naming convention`,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return hints;
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=hints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hints.js","sourceRoot":"","sources":["../../src/scan/hints.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CoverageReport } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Regex-based heuristic scanner for SwiftUI `View` structs and `#Preview`
|
|
4
|
+
* macros (plus legacy `PreviewProvider` structs). Walks the project tree and
|
|
5
|
+
* matches previews to components by file + name heuristics.
|
|
6
|
+
*/
|
|
7
|
+
export declare function scanIos(projectDir: string): Promise<CoverageReport>;
|