@fairfox/polly 0.1.2 → 0.1.4

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 (68) hide show
  1. package/{cli/polly.ts → dist/cli/polly.js} +100 -206
  2. package/dist/cli/polly.js.map +10 -0
  3. package/dist/scripts/build-extension.js +137 -0
  4. package/dist/scripts/build-extension.js.map +10 -0
  5. package/dist/vendor/verify/src/cli.js +2089 -0
  6. package/dist/vendor/verify/src/cli.js.map +16 -0
  7. package/dist/vendor/visualize/src/cli.js +2204 -0
  8. package/dist/vendor/visualize/src/cli.js.map +19 -0
  9. package/package.json +12 -12
  10. package/vendor/analysis/src/extract/adr.ts +0 -212
  11. package/vendor/analysis/src/extract/architecture.ts +0 -160
  12. package/vendor/analysis/src/extract/contexts.ts +0 -298
  13. package/vendor/analysis/src/extract/flows.ts +0 -309
  14. package/vendor/analysis/src/extract/handlers.ts +0 -321
  15. package/vendor/analysis/src/extract/index.ts +0 -9
  16. package/vendor/analysis/src/extract/integrations.ts +0 -329
  17. package/vendor/analysis/src/extract/manifest.ts +0 -298
  18. package/vendor/analysis/src/extract/types.ts +0 -389
  19. package/vendor/analysis/src/index.ts +0 -7
  20. package/vendor/analysis/src/types/adr.ts +0 -53
  21. package/vendor/analysis/src/types/architecture.ts +0 -245
  22. package/vendor/analysis/src/types/core.ts +0 -210
  23. package/vendor/analysis/src/types/index.ts +0 -18
  24. package/vendor/verify/src/adapters/base.ts +0 -164
  25. package/vendor/verify/src/adapters/detection.ts +0 -281
  26. package/vendor/verify/src/adapters/event-bus/index.ts +0 -480
  27. package/vendor/verify/src/adapters/web-extension/index.ts +0 -508
  28. package/vendor/verify/src/adapters/websocket/index.ts +0 -486
  29. package/vendor/verify/src/cli.ts +0 -430
  30. package/vendor/verify/src/codegen/config.ts +0 -354
  31. package/vendor/verify/src/codegen/tla.ts +0 -719
  32. package/vendor/verify/src/config/parser.ts +0 -303
  33. package/vendor/verify/src/config/types.ts +0 -113
  34. package/vendor/verify/src/core/model.ts +0 -267
  35. package/vendor/verify/src/core/primitives.ts +0 -106
  36. package/vendor/verify/src/extract/handlers.ts +0 -2
  37. package/vendor/verify/src/extract/types.ts +0 -2
  38. package/vendor/verify/src/index.ts +0 -150
  39. package/vendor/verify/src/primitives/index.ts +0 -102
  40. package/vendor/verify/src/runner/docker.ts +0 -283
  41. package/vendor/verify/src/types.ts +0 -51
  42. package/vendor/visualize/src/cli.ts +0 -365
  43. package/vendor/visualize/src/codegen/structurizr.ts +0 -770
  44. package/vendor/visualize/src/index.ts +0 -13
  45. package/vendor/visualize/src/runner/export.ts +0 -235
  46. package/vendor/visualize/src/viewer/server.ts +0 -485
  47. /package/dist/{background → src/background}/index.js +0 -0
  48. /package/dist/{background → src/background}/index.js.map +0 -0
  49. /package/dist/{background → src/background}/message-router.js +0 -0
  50. /package/dist/{background → src/background}/message-router.js.map +0 -0
  51. /package/dist/{index.js → src/index.js} +0 -0
  52. /package/dist/{index.js.map → src/index.js.map} +0 -0
  53. /package/dist/{shared → src/shared}/adapters/index.js +0 -0
  54. /package/dist/{shared → src/shared}/adapters/index.js.map +0 -0
  55. /package/dist/{shared → src/shared}/lib/context-helpers.js +0 -0
  56. /package/dist/{shared → src/shared}/lib/context-helpers.js.map +0 -0
  57. /package/dist/{shared → src/shared}/lib/errors.js +0 -0
  58. /package/dist/{shared → src/shared}/lib/errors.js.map +0 -0
  59. /package/dist/{shared → src/shared}/lib/message-bus.js +0 -0
  60. /package/dist/{shared → src/shared}/lib/message-bus.js.map +0 -0
  61. /package/dist/{shared → src/shared}/lib/state.js +0 -0
  62. /package/dist/{shared → src/shared}/lib/state.js.map +0 -0
  63. /package/dist/{shared → src/shared}/lib/test-helpers.js +0 -0
  64. /package/dist/{shared → src/shared}/lib/test-helpers.js.map +0 -0
  65. /package/dist/{shared → src/shared}/state/app-state.js +0 -0
  66. /package/dist/{shared → src/shared}/state/app-state.js.map +0 -0
  67. /package/dist/{shared → src/shared}/types/messages.js +0 -0
  68. /package/dist/{shared → src/shared}/types/messages.js.map +0 -0
@@ -1,212 +0,0 @@
1
- // ADR (Architecture Decision Record) extractor
2
- // Parses ADRs from markdown files following Michael Nygard's format
3
-
4
- import * as fs from "node:fs";
5
- import * as path from "node:path";
6
- import type { ADR, ADRStatus, ADRLink, ADRCollection } from "../types/adr";
7
-
8
- export class ADRExtractor {
9
- private projectRoot: string;
10
-
11
- constructor(projectRoot: string) {
12
- this.projectRoot = projectRoot;
13
- }
14
-
15
- /**
16
- * Extract ADRs from docs/adr directory
17
- */
18
- extract(): ADRCollection {
19
- const adrDir = this.findADRDirectory();
20
-
21
- if (!adrDir || !fs.existsSync(adrDir)) {
22
- return {
23
- adrs: [],
24
- directory: adrDir || path.join(this.projectRoot, "docs", "adr"),
25
- };
26
- }
27
-
28
- const files = fs
29
- .readdirSync(adrDir)
30
- .filter((file) => file.endsWith(".md"))
31
- .map((file) => path.join(adrDir, file));
32
-
33
- const adrs: ADR[] = [];
34
-
35
- for (const file of files) {
36
- try {
37
- const adr = this.parseADR(file);
38
- if (adr) {
39
- adrs.push(adr);
40
- }
41
- } catch (error) {
42
- console.warn(`Failed to parse ADR: ${file}`, error);
43
- }
44
- }
45
-
46
- // Sort by ID
47
- adrs.sort((a, b) => a.id.localeCompare(b.id));
48
-
49
- return {
50
- adrs,
51
- directory: adrDir,
52
- };
53
- }
54
-
55
- /**
56
- * Find ADR directory
57
- */
58
- private findADRDirectory(): string | null {
59
- const candidates = [
60
- path.join(this.projectRoot, "docs", "adr"),
61
- path.join(this.projectRoot, "docs", "architecture", "decisions"),
62
- path.join(this.projectRoot, "adr"),
63
- path.join(this.projectRoot, "architecture", "decisions"),
64
- ];
65
-
66
- for (const candidate of candidates) {
67
- if (fs.existsSync(candidate)) {
68
- return candidate;
69
- }
70
- }
71
-
72
- return null;
73
- }
74
-
75
- /**
76
- * Parse ADR from markdown file
77
- */
78
- private parseADR(filePath: string): ADR | null {
79
- const content = fs.readFileSync(filePath, "utf-8");
80
- const fileName = path.basename(filePath, ".md");
81
-
82
- // Extract ID from filename (e.g., "0001-use-preact.md" → "0001")
83
- const idMatch = fileName.match(/^(\d+)/);
84
- const id = idMatch ? idMatch[1] : fileName;
85
-
86
- // Extract title from first heading
87
- const titleMatch = content.match(/^#\s+(.+)$/m);
88
- const title = titleMatch ? titleMatch[1].trim() : fileName;
89
-
90
- // Extract status
91
- const status = this.extractStatus(content);
92
-
93
- // Extract date
94
- const date = this.extractDate(content);
95
-
96
- // Extract sections
97
- const context = this.extractSection(content, "Context");
98
- const decision = this.extractSection(content, "Decision");
99
- const consequences = this.extractSection(content, "Consequences");
100
-
101
- // Extract alternatives (if present)
102
- const alternativesSection = this.extractSection(content, "Alternatives");
103
- const alternatives = alternativesSection
104
- ? alternativesSection
105
- .split("\n")
106
- .filter((line) => line.trim().startsWith("-"))
107
- .map((line) => line.replace(/^-\s*/, "").trim())
108
- : undefined;
109
-
110
- // Extract links
111
- const links = this.extractLinks(content);
112
-
113
- if (!context || !decision || !consequences) {
114
- // Not a valid ADR format
115
- return null;
116
- }
117
-
118
- return {
119
- id,
120
- title,
121
- status,
122
- date,
123
- context,
124
- decision,
125
- consequences,
126
- alternatives,
127
- links,
128
- source: filePath,
129
- };
130
- }
131
-
132
- /**
133
- * Extract status from content
134
- */
135
- private extractStatus(content: string): ADRStatus {
136
- const statusMatch = content.match(/Status:\s*(\w+)/i);
137
- if (!statusMatch) return "accepted";
138
-
139
- const status = statusMatch[1].toLowerCase();
140
- if (["proposed", "accepted", "deprecated", "superseded"].includes(status)) {
141
- return status as ADRStatus;
142
- }
143
-
144
- return "accepted";
145
- }
146
-
147
- /**
148
- * Extract date from content
149
- */
150
- private extractDate(content: string): string {
151
- // Try to find date in various formats
152
- const dateMatch =
153
- content.match(/Date:\s*(\d{4}-\d{2}-\d{2})/i) || content.match(/(\d{4}-\d{2}-\d{2})/i);
154
-
155
- return dateMatch ? dateMatch[1] : new Date().toISOString().split("T")[0];
156
- }
157
-
158
- /**
159
- * Extract section content
160
- */
161
- private extractSection(content: string, sectionName: string): string {
162
- // Match section heading and capture content until next heading or end
163
- const regex = new RegExp(`##\\s+${sectionName}\\s*\\n([\\s\\S]*?)(?=\\n##|$)`, "i");
164
- const match = content.match(regex);
165
-
166
- return match ? match[1].trim() : "";
167
- }
168
-
169
- /**
170
- * Extract links to other ADRs
171
- */
172
- private extractLinks(content: string): ADRLink[] {
173
- const links: ADRLink[] = [];
174
-
175
- // Look for supersedes/superseded-by links
176
- const supersedesMatch = content.match(/Supersedes:\s*ADR-(\d+)/gi);
177
- if (supersedesMatch) {
178
- for (const match of supersedesMatch) {
179
- const idMatch = match.match(/ADR-(\d+)/);
180
- if (idMatch) {
181
- links.push({
182
- type: "supersedes",
183
- adrId: idMatch[1],
184
- });
185
- }
186
- }
187
- }
188
-
189
- const supersededByMatch = content.match(/Superseded by:\s*ADR-(\d+)/gi);
190
- if (supersededByMatch) {
191
- for (const match of supersededByMatch) {
192
- const idMatch = match.match(/ADR-(\d+)/);
193
- if (idMatch) {
194
- links.push({
195
- type: "superseded-by",
196
- adrId: idMatch[1],
197
- });
198
- }
199
- }
200
- }
201
-
202
- return links;
203
- }
204
- }
205
-
206
- /**
207
- * Extract ADRs from project
208
- */
209
- export function extractADRs(projectRoot: string): ADRCollection {
210
- const extractor = new ADRExtractor(projectRoot);
211
- return extractor.extract();
212
- }
@@ -1,160 +0,0 @@
1
- // Architecture analysis - main orchestrator
2
-
3
- import * as fs from "node:fs";
4
- import * as path from "node:path";
5
- import type { ArchitectureAnalysis, ContextInfo } from "../types/architecture";
6
- import { ManifestParser } from "./manifest";
7
- import { ContextAnalyzer } from "./contexts";
8
- import { FlowAnalyzer } from "./flows";
9
- import { IntegrationAnalyzer } from "./integrations";
10
- import { HandlerExtractor } from "./handlers";
11
- import { extractADRs } from "./adr";
12
-
13
- export interface ArchitectureAnalysisOptions {
14
- /** Path to tsconfig.json */
15
- tsConfigPath: string;
16
-
17
- /** Project root directory (where manifest.json is) */
18
- projectRoot: string;
19
-
20
- /** Optional: Override detected contexts */
21
- contexts?: string[];
22
- }
23
-
24
- /**
25
- * Comprehensive architecture analyzer
26
- */
27
- export class ArchitectureAnalyzer {
28
- private options: ArchitectureAnalysisOptions;
29
-
30
- constructor(options: ArchitectureAnalysisOptions) {
31
- this.options = options;
32
- }
33
-
34
- /**
35
- * Perform complete architecture analysis
36
- */
37
- async analyze(): Promise<ArchitectureAnalysis> {
38
- // 1. Parse manifest.json
39
- const manifestParser = new ManifestParser(this.options.projectRoot);
40
- const manifest = manifestParser.parse();
41
- const entryPoints = manifestParser.getContextEntryPoints();
42
-
43
- // 2. Extract message handlers
44
- const handlerExtractor = new HandlerExtractor(this.options.tsConfigPath);
45
- const { handlers } = handlerExtractor.extractHandlers();
46
-
47
- // 3. Analyze each context
48
- const contextAnalyzer = new ContextAnalyzer(this.options.tsConfigPath);
49
- const contexts: Record<string, ContextInfo> = {};
50
-
51
- for (const [contextType, entryPoint] of Object.entries(entryPoints)) {
52
- try {
53
- const contextInfo = contextAnalyzer.analyzeContext(contextType, entryPoint, handlers);
54
- contexts[contextType] = contextInfo;
55
- } catch (error) {
56
- console.warn(`Failed to analyze context ${contextType}: ${error}`);
57
- }
58
- }
59
-
60
- // 4. Analyze message flows
61
- const flowAnalyzer = new FlowAnalyzer(this.options.tsConfigPath, handlers);
62
- const messageFlows = flowAnalyzer.analyzeFlows();
63
-
64
- // 5. Analyze external integrations
65
- const integrationAnalyzer = new IntegrationAnalyzer(this.options.tsConfigPath);
66
- const integrations = integrationAnalyzer.analyzeIntegrations();
67
-
68
- // 6. Merge external API calls into context info
69
- this.mergeExternalAPIsIntoContexts(contexts, integrations);
70
-
71
- // 7. Extract ADRs (Architecture Decision Records)
72
- const adrs = extractADRs(this.options.projectRoot);
73
-
74
- // 8. Extract repository info
75
- const repository = this.extractRepositoryInfo();
76
-
77
- return {
78
- system: {
79
- name: manifest.name,
80
- version: manifest.version,
81
- description: manifest.description,
82
- },
83
- manifest,
84
- contexts,
85
- messageFlows,
86
- integrations,
87
- adrs: adrs.adrs.length > 0 ? adrs : undefined,
88
- repository,
89
- };
90
- }
91
-
92
- /**
93
- * Merge external API calls into their respective contexts
94
- */
95
- private mergeExternalAPIsIntoContexts(
96
- contexts: Record<string, ContextInfo>,
97
- integrations: ExternalIntegration[]
98
- ): void {
99
- for (const integration of integrations) {
100
- if (integration.calls) {
101
- for (const call of integration.calls) {
102
- // Find which context this call belongs to
103
- for (const [contextType, contextInfo] of Object.entries(contexts)) {
104
- if (call.location.file.includes(`/${contextType}/`)) {
105
- contextInfo.externalAPIs.push(call);
106
- break;
107
- }
108
- }
109
- }
110
- }
111
- }
112
- }
113
-
114
- /**
115
- * Extract repository information from package.json
116
- */
117
- private extractRepositoryInfo(): ArchitectureAnalysis["repository"] {
118
- const packageJsonPath = path.join(this.options.projectRoot, "package.json");
119
-
120
- if (!fs.existsSync(packageJsonPath)) {
121
- return undefined;
122
- }
123
-
124
- try {
125
- const content = fs.readFileSync(packageJsonPath, "utf-8");
126
- const packageJson = JSON.parse(content);
127
-
128
- if (packageJson.repository) {
129
- if (typeof packageJson.repository === "string") {
130
- return {
131
- url: packageJson.repository,
132
- type: "git",
133
- };
134
- }
135
-
136
- return {
137
- url: packageJson.repository.url || packageJson.repository,
138
- type: packageJson.repository.type || "git",
139
- };
140
- }
141
- } catch (error) {
142
- console.warn(`Failed to parse package.json: ${error}`);
143
- }
144
-
145
- return undefined;
146
- }
147
- }
148
-
149
- /**
150
- * Convenience function to analyze architecture
151
- */
152
- export async function analyzeArchitecture(
153
- options: ArchitectureAnalysisOptions
154
- ): Promise<ArchitectureAnalysis> {
155
- const analyzer = new ArchitectureAnalyzer(options);
156
- return analyzer.analyze();
157
- }
158
-
159
- // Fix: Import ExternalIntegration type
160
- import type { ExternalIntegration } from "../types/architecture";
@@ -1,298 +0,0 @@
1
- // Context analysis - analyze individual execution contexts
2
-
3
- import { Project, Node, SyntaxKind } from "ts-morph";
4
- import type { ContextInfo, ComponentInfo } from "../types/architecture";
5
- import type { MessageHandler } from "../types/core";
6
-
7
- export class ContextAnalyzer {
8
- private project: Project;
9
-
10
- constructor(tsConfigPath: string) {
11
- this.project = new Project({
12
- tsConfigFilePath: tsConfigPath,
13
- });
14
- }
15
-
16
- /**
17
- * Analyze a specific context given its entry point
18
- */
19
- analyzeContext(contextType: string, entryPoint: string, handlers: MessageHandler[]): ContextInfo {
20
- const sourceFile = this.project.getSourceFile(entryPoint);
21
-
22
- if (!sourceFile) {
23
- throw new Error(`Could not find source file: ${entryPoint}`);
24
- }
25
-
26
- // Extract Chrome API usage
27
- const chromeAPIs = this.extractChromeAPIs(sourceFile);
28
-
29
- // Extract dependencies
30
- const dependencies = this.extractDependencies(sourceFile);
31
-
32
- // Extract JSDoc description
33
- const description = this.extractDescription(sourceFile);
34
-
35
- // Extract components (for UI contexts)
36
- const components = this.isUIContext(contextType)
37
- ? this.extractComponents(sourceFile)
38
- : undefined;
39
-
40
- // Filter handlers for this context
41
- const contextHandlers = handlers.filter((h) => h.node === contextType);
42
-
43
- return {
44
- type: contextType,
45
- entryPoint,
46
- handlers: contextHandlers,
47
- chromeAPIs,
48
- externalAPIs: [], // Will be filled by integration analyzer
49
- components,
50
- dependencies,
51
- description,
52
- };
53
- }
54
-
55
- /**
56
- * Extract Chrome API usage from source file
57
- */
58
- private extractChromeAPIs(sourceFile: any): string[] {
59
- const apis = new Set<string>();
60
-
61
- sourceFile.forEachDescendant((node: any) => {
62
- if (Node.isPropertyAccessExpression(node)) {
63
- const text = node.getText();
64
-
65
- // Match chrome.* API calls
66
- if (text.startsWith("chrome.")) {
67
- // Extract API namespace (e.g., "chrome.tabs", "chrome.storage.local")
68
- const match = text.match(/^chrome\.([^.(]+(?:\.[^.(]+)?)/);
69
- if (match) {
70
- apis.add(match[1]);
71
- }
72
- }
73
-
74
- // Also match browser.* for Firefox compatibility
75
- if (text.startsWith("browser.")) {
76
- const match = text.match(/^browser\.([^.(]+(?:\.[^.(]+)?)/);
77
- if (match) {
78
- apis.add(match[1]);
79
- }
80
- }
81
-
82
- // Match bus.adapters.* pattern (framework abstraction over Chrome APIs)
83
- // e.g., bus.adapters.storage.get() -> storage
84
- // bus.adapters.tabs.query() -> tabs
85
- if (text.includes("bus.adapters.")) {
86
- const match = text.match(/bus\.adapters\.([^.(]+)/);
87
- if (match) {
88
- apis.add(match[1]);
89
- }
90
- }
91
- }
92
- });
93
-
94
- return Array.from(apis).sort();
95
- }
96
-
97
- /**
98
- * Extract import dependencies
99
- */
100
- private extractDependencies(sourceFile: any): string[] {
101
- const deps: string[] = [];
102
-
103
- for (const importDecl of sourceFile.getImportDeclarations()) {
104
- const moduleSpecifier = importDecl.getModuleSpecifierValue();
105
- deps.push(moduleSpecifier);
106
- }
107
-
108
- return deps;
109
- }
110
-
111
- /**
112
- * Extract JSDoc description from file
113
- */
114
- private extractDescription(sourceFile: any): string | undefined {
115
- // Look for file-level JSDoc comment
116
- const firstStatement = sourceFile.getStatements()[0];
117
- if (!firstStatement) return undefined;
118
-
119
- const leadingComments = firstStatement.getLeadingCommentRanges();
120
- if (leadingComments.length === 0) return undefined;
121
-
122
- const comment = leadingComments[0].getText();
123
-
124
- // Extract description from JSDoc
125
- const descMatch = comment.match(/@description\s+(.+?)(?:\n|$)/s);
126
- if (descMatch) {
127
- return descMatch[1].trim();
128
- }
129
-
130
- // Or just take the first line of the comment
131
- const lines = comment
132
- .split("\n")
133
- .map((l: string) => l.replace(/^[\s*]+/, "").trim())
134
- .filter((l: string) => l && !l.startsWith("@"));
135
-
136
- return lines[0] || undefined;
137
- }
138
-
139
- /**
140
- * Extract React/Preact components from source file
141
- */
142
- private extractComponents(sourceFile: any): ComponentInfo[] {
143
- const components: ComponentInfo[] = [];
144
-
145
- sourceFile.forEachDescendant((node: any) => {
146
- // Function components
147
- if (Node.isFunctionDeclaration(node)) {
148
- const name = node.getName();
149
- if (name && this.looksLikeComponent(name, node)) {
150
- components.push({
151
- name,
152
- type: "function",
153
- filePath: sourceFile.getFilePath(),
154
- line: node.getStartLineNumber(),
155
- props: this.extractProps(node),
156
- description: this.extractJSDocDescription(node),
157
- });
158
- }
159
- }
160
-
161
- // Arrow function components (const Foo = () => ...)
162
- if (Node.isVariableDeclaration(node)) {
163
- const name = node.getName();
164
- const initializer = node.getInitializer();
165
-
166
- if (
167
- name &&
168
- initializer &&
169
- (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))
170
- ) {
171
- if (this.looksLikeComponent(name, initializer)) {
172
- components.push({
173
- name,
174
- type: "function",
175
- filePath: sourceFile.getFilePath(),
176
- line: node.getStartLineNumber(),
177
- props: this.extractProps(initializer),
178
- description: this.extractJSDocDescription(node),
179
- });
180
- }
181
- }
182
- }
183
-
184
- // Class components
185
- if (Node.isClassDeclaration(node)) {
186
- const name = node.getName();
187
- if (name && this.looksLikeClassComponent(node)) {
188
- components.push({
189
- name,
190
- type: "class",
191
- filePath: sourceFile.getFilePath(),
192
- line: node.getStartLineNumber(),
193
- props: this.extractPropsFromClass(node),
194
- description: this.extractJSDocDescription(node),
195
- });
196
- }
197
- }
198
- });
199
-
200
- return components;
201
- }
202
-
203
- /**
204
- * Check if a function looks like a React/Preact component
205
- */
206
- private looksLikeComponent(name: string, node: any): boolean {
207
- // Component names should start with uppercase
208
- if (!/^[A-Z]/.test(name)) return false;
209
-
210
- // Check if it returns JSX
211
- const body = node.getBody();
212
- if (!body) return false;
213
-
214
- let hasJSX = false;
215
-
216
- if (Node.isBlock(body)) {
217
- body.forEachDescendant((child: any) => {
218
- if (Node.isJsxElement(child) || Node.isJsxSelfClosingElement(child)) {
219
- hasJSX = true;
220
- }
221
- });
222
- } else {
223
- // Arrow function with implicit return
224
- if (Node.isJsxElement(body) || Node.isJsxSelfClosingElement(body)) {
225
- hasJSX = true;
226
- }
227
- }
228
-
229
- return hasJSX;
230
- }
231
-
232
- /**
233
- * Check if a class looks like a React component
234
- */
235
- private looksLikeClassComponent(node: any): boolean {
236
- const extendedTypes = node.getExtends();
237
- if (!extendedTypes) return false;
238
-
239
- const extendsText = extendedTypes.getText();
240
- return /Component|PureComponent/.test(extendsText);
241
- }
242
-
243
- /**
244
- * Extract props from function component
245
- */
246
- private extractProps(node: any): string[] {
247
- const params = node.getParameters();
248
- if (params.length === 0) return [];
249
-
250
- const propsParam = params[0];
251
- const type = propsParam.getType();
252
-
253
- const props: string[] = [];
254
- for (const prop of type.getProperties()) {
255
- props.push(prop.getName());
256
- }
257
-
258
- return props;
259
- }
260
-
261
- /**
262
- * Extract props from class component
263
- */
264
- private extractPropsFromClass(node: any): string[] {
265
- const extendedTypes = node.getExtends();
266
- if (!extendedTypes) return [];
267
-
268
- const typeArgs = extendedTypes.getType().getTypeArguments();
269
- if (typeArgs.length === 0) return [];
270
-
271
- const propsType = typeArgs[0];
272
- const props: string[] = [];
273
-
274
- for (const prop of propsType.getProperties()) {
275
- props.push(prop.getName());
276
- }
277
-
278
- return props;
279
- }
280
-
281
- /**
282
- * Extract JSDoc description from node
283
- */
284
- private extractJSDocDescription(node: any): string | undefined {
285
- const jsDocs = node.getJsDocs();
286
- if (jsDocs.length === 0) return undefined;
287
-
288
- const description = jsDocs[0].getDescription().trim();
289
- return description || undefined;
290
- }
291
-
292
- /**
293
- * Check if context is a UI context
294
- */
295
- private isUIContext(contextType: string): boolean {
296
- return ["popup", "options", "devtools"].includes(contextType);
297
- }
298
- }