@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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +82 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/doctor.d.ts +33 -0
  7. package/dist/commands/doctor.js +690 -0
  8. package/dist/commands/doctor.js.map +1 -0
  9. package/dist/commands/init.d.ts +27 -0
  10. package/dist/commands/init.js +415 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/config.d.ts +33 -0
  13. package/dist/config.js +21 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/engines/android.d.ts +70 -0
  16. package/dist/engines/android.js +265 -0
  17. package/dist/engines/android.js.map +1 -0
  18. package/dist/engines/git.d.ts +4 -0
  19. package/dist/engines/git.js +13 -0
  20. package/dist/engines/git.js.map +1 -0
  21. package/dist/engines/ios.d.ts +55 -0
  22. package/dist/engines/ios.js +195 -0
  23. package/dist/engines/ios.js.map +1 -0
  24. package/dist/errors.d.ts +12 -0
  25. package/dist/errors.js +69 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/gradle/catalog.d.ts +110 -0
  28. package/dist/gradle/catalog.js +413 -0
  29. package/dist/gradle/catalog.js.map +1 -0
  30. package/dist/ios/snapshotTestClass.d.ts +59 -0
  31. package/dist/ios/snapshotTestClass.js +195 -0
  32. package/dist/ios/snapshotTestClass.js.map +1 -0
  33. package/dist/manifest.d.ts +32 -0
  34. package/dist/manifest.js +23 -0
  35. package/dist/manifest.js.map +1 -0
  36. package/dist/mcp/server.d.ts +1 -0
  37. package/dist/mcp/server.js +226 -0
  38. package/dist/mcp/server.js.map +1 -0
  39. package/dist/naming.d.ts +18 -0
  40. package/dist/naming.js +34 -0
  41. package/dist/naming.js.map +1 -0
  42. package/dist/scan/android.d.ts +7 -0
  43. package/dist/scan/android.js +222 -0
  44. package/dist/scan/android.js.map +1 -0
  45. package/dist/scan/hints.d.ts +25 -0
  46. package/dist/scan/hints.js +338 -0
  47. package/dist/scan/hints.js.map +1 -0
  48. package/dist/scan/ios.d.ts +7 -0
  49. package/dist/scan/ios.js +201 -0
  50. package/dist/scan/ios.js.map +1 -0
  51. package/dist/scan/types.d.ts +52 -0
  52. package/dist/scan/types.js +7 -0
  53. package/dist/scan/types.js.map +1 -0
  54. package/dist/site/build.d.ts +17 -0
  55. package/dist/site/build.js +0 -0
  56. package/dist/site/build.js.map +1 -0
  57. package/dist/versions.d.ts +71 -0
  58. package/dist/versions.js +334 -0
  59. package/dist/versions.js.map +1 -0
  60. 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>;