@nuasite/cms 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/README.md +237 -0
- package/dist/src/build-processor.d.ts +20 -0
- package/dist/src/build-processor.d.ts.map +1 -0
- package/dist/src/collection-scanner.d.ts +6 -0
- package/dist/src/collection-scanner.d.ts.map +1 -0
- package/dist/src/component-registry.d.ts +63 -0
- package/dist/src/component-registry.d.ts.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/dev-middleware.d.ts +20 -0
- package/dist/src/dev-middleware.d.ts.map +1 -0
- package/dist/src/editor/ai.d.ts +60 -0
- package/dist/src/editor/ai.d.ts.map +1 -0
- package/dist/src/editor/api.d.ts +140 -0
- package/dist/src/editor/api.d.ts.map +1 -0
- package/dist/src/editor/color-utils.d.ts +106 -0
- package/dist/src/editor/color-utils.d.ts.map +1 -0
- package/dist/src/editor/components/ai-chat.d.ts +11 -0
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
- package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
- package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
- package/dist/src/editor/components/attribute-editor.d.ts +5 -0
- package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
- package/dist/src/editor/components/block-editor.d.ts +12 -0
- package/dist/src/editor/components/block-editor.d.ts.map +1 -0
- package/dist/src/editor/components/collections-browser.d.ts +2 -0
- package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
- package/dist/src/editor/components/color-toolbar.d.ts +12 -0
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
- package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
- package/dist/src/editor/components/create-page-modal.d.ts +2 -0
- package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
- package/dist/src/editor/components/editable-highlights.d.ts +9 -0
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
- package/dist/src/editor/components/error-boundary.d.ts +32 -0
- package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
- package/dist/src/editor/components/fields.d.ts +75 -0
- package/dist/src/editor/components/fields.d.ts.map +1 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
- package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
- package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/image-overlay.d.ts +12 -0
- package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
- package/dist/src/editor/components/media-library.d.ts +2 -0
- package/dist/src/editor/components/media-library.d.ts.map +1 -0
- package/dist/src/editor/components/outline.d.ts +21 -0
- package/dist/src/editor/components/outline.d.ts.map +1 -0
- package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
- package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
- package/dist/src/editor/components/seo-editor.d.ts +2 -0
- package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
- package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast.d.ts +7 -0
- package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
- package/dist/src/editor/components/toast/types.d.ts +7 -0
- package/dist/src/editor/components/toast/types.d.ts.map +1 -0
- package/dist/src/editor/components/toolbar.d.ts +21 -0
- package/dist/src/editor/components/toolbar.d.ts.map +1 -0
- package/dist/src/editor/config.d.ts +4 -0
- package/dist/src/editor/config.d.ts.map +1 -0
- package/dist/src/editor/constants.d.ts +101 -0
- package/dist/src/editor/constants.d.ts.map +1 -0
- package/dist/src/editor/context.d.ts +14 -0
- package/dist/src/editor/context.d.ts.map +1 -0
- package/dist/src/editor/dom.d.ts +77 -0
- package/dist/src/editor/dom.d.ts.map +1 -0
- package/dist/src/editor/editor.d.ts +64 -0
- package/dist/src/editor/editor.d.ts.map +1 -0
- package/dist/src/editor/history.d.ts +20 -0
- package/dist/src/editor/history.d.ts.map +1 -0
- package/dist/src/editor/hooks/index.d.ts +14 -0
- package/dist/src/editor/hooks/index.d.ts.map +1 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
- package/dist/src/editor/hooks/utils.d.ts +32 -0
- package/dist/src/editor/hooks/utils.d.ts.map +1 -0
- package/dist/src/editor/index.d.ts +12 -0
- package/dist/src/editor/index.d.ts.map +1 -0
- package/dist/src/editor/lib/cn.d.ts +3 -0
- package/dist/src/editor/lib/cn.d.ts.map +1 -0
- package/dist/src/editor/manifest.d.ts +19 -0
- package/dist/src/editor/manifest.d.ts.map +1 -0
- package/dist/src/editor/markdown-api.d.ts +36 -0
- package/dist/src/editor/markdown-api.d.ts.map +1 -0
- package/dist/src/editor/signals.d.ts +242 -0
- package/dist/src/editor/signals.d.ts.map +1 -0
- package/dist/src/editor/storage.d.ts +27 -0
- package/dist/src/editor/storage.d.ts.map +1 -0
- package/dist/src/editor/text-styling.d.ts +350 -0
- package/dist/src/editor/text-styling.d.ts.map +1 -0
- package/dist/src/editor/themes.d.ts +38 -0
- package/dist/src/editor/themes.d.ts.map +1 -0
- package/dist/src/editor/types.d.ts +454 -0
- package/dist/src/editor/types.d.ts.map +1 -0
- package/dist/src/error-collector.d.ts +56 -0
- package/dist/src/error-collector.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +34 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -0
- package/dist/src/handlers/markdown-ops.d.ts +41 -0
- package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
- package/dist/src/handlers/request-utils.d.ts +20 -0
- package/dist/src/handlers/request-utils.d.ts.map +1 -0
- package/dist/src/handlers/source-writer.d.ts +51 -0
- package/dist/src/handlers/source-writer.d.ts.map +1 -0
- package/dist/src/html-processor.d.ts +63 -0
- package/dist/src/html-processor.d.ts.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/manifest-writer.d.ts +111 -0
- package/dist/src/manifest-writer.d.ts.map +1 -0
- package/dist/src/media/contember.d.ts +15 -0
- package/dist/src/media/contember.d.ts.map +1 -0
- package/dist/src/media/local.d.ts +9 -0
- package/dist/src/media/local.d.ts.map +1 -0
- package/dist/src/media/s3.d.ts +12 -0
- package/dist/src/media/s3.d.ts.map +1 -0
- package/dist/src/media/types.d.ts +40 -0
- package/dist/src/media/types.d.ts.map +1 -0
- package/dist/src/preview-generator.d.ts +19 -0
- package/dist/src/preview-generator.d.ts.map +1 -0
- package/dist/src/seo-processor.d.ts +23 -0
- package/dist/src/seo-processor.d.ts.map +1 -0
- package/dist/src/source-finder/ast-extractors.d.ts +35 -0
- package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
- package/dist/src/source-finder/ast-parser.d.ts +16 -0
- package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
- package/dist/src/source-finder/cache.d.ts +18 -0
- package/dist/src/source-finder/cache.d.ts.map +1 -0
- package/dist/src/source-finder/collection-finder.d.ts +29 -0
- package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
- package/dist/src/source-finder/element-finder.d.ts +42 -0
- package/dist/src/source-finder/element-finder.d.ts.map +1 -0
- package/dist/src/source-finder/image-finder.d.ts +24 -0
- package/dist/src/source-finder/image-finder.d.ts.map +1 -0
- package/dist/src/source-finder/index.d.ts +9 -0
- package/dist/src/source-finder/index.d.ts.map +1 -0
- package/dist/src/source-finder/search-index.d.ts +27 -0
- package/dist/src/source-finder/search-index.d.ts.map +1 -0
- package/dist/src/source-finder/snippet-utils.d.ts +90 -0
- package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
- package/dist/src/source-finder/source-lookup.d.ts +16 -0
- package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
- package/dist/src/source-finder/types.d.ts +167 -0
- package/dist/src/source-finder/types.d.ts.map +1 -0
- package/dist/src/source-finder/variable-extraction.d.ts +37 -0
- package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
- package/dist/src/tailwind-colors.d.ts +54 -0
- package/dist/src/tailwind-colors.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +61 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/vite-plugin.d.ts +14 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -0
- package/src/build-processor.ts +784 -0
- package/src/collection-scanner.ts +304 -0
- package/src/component-registry.ts +393 -0
- package/src/config.ts +74 -0
- package/src/dev-middleware.ts +525 -0
- package/src/dist/src/tsconfig.tsbuildinfo +1 -0
- package/src/editor/ai.ts +185 -0
- package/src/editor/api.ts +513 -0
- package/src/editor/color-utils.ts +556 -0
- package/src/editor/components/ai-chat.tsx +632 -0
- package/src/editor/components/ai-tooltip.tsx +179 -0
- package/src/editor/components/attribute-editor.tsx +596 -0
- package/src/editor/components/block-editor.tsx +546 -0
- package/src/editor/components/collections-browser.tsx +248 -0
- package/src/editor/components/color-toolbar.tsx +314 -0
- package/src/editor/components/confirm-dialog.tsx +69 -0
- package/src/editor/components/create-page-modal.tsx +163 -0
- package/src/editor/components/editable-highlights.tsx +260 -0
- package/src/editor/components/error-boundary.tsx +87 -0
- package/src/editor/components/fields.tsx +387 -0
- package/src/editor/components/frontmatter-fields.tsx +469 -0
- package/src/editor/components/highlight-overlay.ts +229 -0
- package/src/editor/components/image-overlay.tsx +230 -0
- package/src/editor/components/markdown-editor-overlay.tsx +505 -0
- package/src/editor/components/markdown-inline-editor.tsx +780 -0
- package/src/editor/components/media-library.tsx +297 -0
- package/src/editor/components/outline.tsx +402 -0
- package/src/editor/components/redirect-countdown.tsx +45 -0
- package/src/editor/components/seo-editor.tsx +498 -0
- package/src/editor/components/text-style-toolbar.tsx +362 -0
- package/src/editor/components/toast/toast-container.tsx +15 -0
- package/src/editor/components/toast/toast.tsx +49 -0
- package/src/editor/components/toast/types.ts +7 -0
- package/src/editor/components/toolbar.tsx +366 -0
- package/src/editor/config.ts +12 -0
- package/src/editor/constants.ts +106 -0
- package/src/editor/context.tsx +38 -0
- package/src/editor/dom.ts +357 -0
- package/src/editor/editor.ts +1510 -0
- package/src/editor/env.d.ts +4 -0
- package/src/editor/history.ts +355 -0
- package/src/editor/hooks/index.ts +19 -0
- package/src/editor/hooks/useAIHandlers.ts +345 -0
- package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
- package/src/editor/hooks/useElementDetection.ts +284 -0
- package/src/editor/hooks/useImageHoverDetection.ts +102 -0
- package/src/editor/hooks/useTextSelection.ts +187 -0
- package/src/editor/hooks/useTooltipState.ts +126 -0
- package/src/editor/hooks/utils.ts +101 -0
- package/src/editor/index.tsx +481 -0
- package/src/editor/lib/cn.ts +4 -0
- package/src/editor/manifest.ts +25 -0
- package/src/editor/markdown-api.ts +209 -0
- package/src/editor/signals.ts +1351 -0
- package/src/editor/storage.ts +266 -0
- package/src/editor/styles.css +465 -0
- package/src/editor/text-styling.ts +773 -0
- package/src/editor/themes.ts +210 -0
- package/src/editor/types.ts +591 -0
- package/src/error-collector.ts +106 -0
- package/src/handlers/component-ops.ts +463 -0
- package/src/handlers/markdown-ops.ts +202 -0
- package/src/handlers/request-utils.ts +151 -0
- package/src/handlers/source-writer.ts +649 -0
- package/src/html-processor.ts +1108 -0
- package/src/index.ts +284 -0
- package/src/manifest-writer.ts +371 -0
- package/src/media/contember.ts +84 -0
- package/src/media/local.ts +114 -0
- package/src/media/s3.ts +133 -0
- package/src/media/types.ts +33 -0
- package/src/preview-generator.ts +293 -0
- package/src/seo-processor.ts +567 -0
- package/src/source-finder/ast-extractors.ts +185 -0
- package/src/source-finder/ast-parser.ts +150 -0
- package/src/source-finder/cache.ts +76 -0
- package/src/source-finder/collection-finder.ts +335 -0
- package/src/source-finder/cross-file-tracker.ts +741 -0
- package/src/source-finder/element-finder.ts +387 -0
- package/src/source-finder/image-finder.ts +283 -0
- package/src/source-finder/index.ts +37 -0
- package/src/source-finder/search-index.ts +525 -0
- package/src/source-finder/snippet-utils.ts +668 -0
- package/src/source-finder/source-lookup.ts +200 -0
- package/src/source-finder/types.ts +210 -0
- package/src/source-finder/variable-extraction.ts +406 -0
- package/src/tailwind-colors.ts +874 -0
- package/src/tsconfig.json +25 -0
- package/src/types.ts +406 -0
- package/src/utils.ts +186 -0
- package/src/vite-plugin.ts +42 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { getProjectRoot } from '../config'
|
|
5
|
+
import { buildDefinitionPath, parseExpressionPath } from './ast-extractors'
|
|
6
|
+
import { getCachedParsedFile } from './ast-parser'
|
|
7
|
+
import { findComponentProp, findExpressionProp, findSpreadProp } from './element-finder'
|
|
8
|
+
import { normalizeText } from './snippet-utils'
|
|
9
|
+
import type { ImportInfo, SourceLocation, VariableDefinition } from './types'
|
|
10
|
+
import { escapeRegex } from '../utils'
|
|
11
|
+
import { getExportedDefinitions, resolveImportPath } from './variable-extraction'
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Expression Prop Search
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Search for a component usage with an expression prop across all files.
|
|
19
|
+
* When we find an expression like {items[0]} in a component where items comes from props,
|
|
20
|
+
* we search for where that component is used and track the expression prop back.
|
|
21
|
+
* Supports multi-level prop drilling with a depth limit.
|
|
22
|
+
*
|
|
23
|
+
* @param componentFileName - The file name of the component (e.g., 'Nav.astro')
|
|
24
|
+
* @param propName - The prop name we're looking for (e.g., 'items')
|
|
25
|
+
* @param expressionPath - The full expression path (e.g., 'items[0]')
|
|
26
|
+
* @param searchText - The text content we're searching for
|
|
27
|
+
* @param depth - Current recursion depth (default 0, max 5)
|
|
28
|
+
* @returns Source location if found
|
|
29
|
+
*/
|
|
30
|
+
export async function searchForExpressionProp(
|
|
31
|
+
componentFileName: string,
|
|
32
|
+
propName: string,
|
|
33
|
+
expressionPath: string,
|
|
34
|
+
searchText: string,
|
|
35
|
+
depth: number = 0,
|
|
36
|
+
): Promise<SourceLocation | undefined> {
|
|
37
|
+
// Limit recursion depth to prevent infinite loops
|
|
38
|
+
if (depth > 5) return undefined
|
|
39
|
+
|
|
40
|
+
const srcDir = path.join(getProjectRoot(), 'src')
|
|
41
|
+
const searchDirs = [
|
|
42
|
+
path.join(srcDir, 'pages'),
|
|
43
|
+
path.join(srcDir, 'components'),
|
|
44
|
+
path.join(srcDir, 'layouts'),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// Extract the component name from file name (e.g., 'Nav.astro' -> 'Nav')
|
|
48
|
+
const componentName = path.basename(componentFileName, '.astro')
|
|
49
|
+
const normalizedSearch = normalizeText(searchText)
|
|
50
|
+
|
|
51
|
+
for (const dir of searchDirs) {
|
|
52
|
+
try {
|
|
53
|
+
const result = await searchDirForExpressionProp(
|
|
54
|
+
dir,
|
|
55
|
+
componentName,
|
|
56
|
+
propName,
|
|
57
|
+
expressionPath,
|
|
58
|
+
normalizedSearch,
|
|
59
|
+
searchText,
|
|
60
|
+
depth,
|
|
61
|
+
)
|
|
62
|
+
if (result) return result
|
|
63
|
+
} catch {
|
|
64
|
+
// Directory doesn't exist, continue
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function searchDirForExpressionProp(
|
|
72
|
+
dir: string,
|
|
73
|
+
componentName: string,
|
|
74
|
+
propName: string,
|
|
75
|
+
expressionPath: string,
|
|
76
|
+
normalizedSearch: string,
|
|
77
|
+
searchText: string,
|
|
78
|
+
depth: number,
|
|
79
|
+
): Promise<SourceLocation | undefined> {
|
|
80
|
+
try {
|
|
81
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
82
|
+
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const fullPath = path.join(dir, entry.name)
|
|
85
|
+
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
const result = await searchDirForExpressionProp(
|
|
88
|
+
fullPath,
|
|
89
|
+
componentName,
|
|
90
|
+
propName,
|
|
91
|
+
expressionPath,
|
|
92
|
+
normalizedSearch,
|
|
93
|
+
searchText,
|
|
94
|
+
depth,
|
|
95
|
+
)
|
|
96
|
+
if (result) return result
|
|
97
|
+
} else if (entry.isFile() && entry.name.endsWith('.astro')) {
|
|
98
|
+
const cached = await getCachedParsedFile(fullPath)
|
|
99
|
+
if (!cached) continue
|
|
100
|
+
|
|
101
|
+
// First, try to find expression prop usage: <Nav items={navItems} />
|
|
102
|
+
const exprPropMatch = findExpressionProp(cached.ast, componentName, propName)
|
|
103
|
+
|
|
104
|
+
if (exprPropMatch) {
|
|
105
|
+
// The expression text might be a simple variable like 'navItems'
|
|
106
|
+
const exprText = exprPropMatch.expressionText
|
|
107
|
+
|
|
108
|
+
// Build the corresponding path in the parent's variable definitions
|
|
109
|
+
// e.g., if expressionPath is 'items[0]' and exprText is 'navItems',
|
|
110
|
+
// we look for 'navItems[0]' in the parent's definitions
|
|
111
|
+
const parentPath = expressionPath.replace(/^[^.[]+/, exprText)
|
|
112
|
+
|
|
113
|
+
// Check if the value is in local variable definitions
|
|
114
|
+
for (const def of cached.variableDefinitions) {
|
|
115
|
+
const defPath = buildDefinitionPath(def)
|
|
116
|
+
if (defPath === parentPath) {
|
|
117
|
+
const normalizedDef = normalizeText(def.value)
|
|
118
|
+
if (normalizedDef === normalizedSearch) {
|
|
119
|
+
return {
|
|
120
|
+
file: path.relative(getProjectRoot(), fullPath),
|
|
121
|
+
line: def.line,
|
|
122
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
123
|
+
type: 'variable',
|
|
124
|
+
variableName: defPath,
|
|
125
|
+
definitionLine: def.line,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if exprText is itself from props (multi-level prop drilling)
|
|
132
|
+
const baseVar = exprText.match(/^(\w+)/)?.[1]
|
|
133
|
+
if (baseVar && cached.propAliases.has(baseVar)) {
|
|
134
|
+
const actualPropName = cached.propAliases.get(baseVar)!
|
|
135
|
+
// Recursively search for where this component is used
|
|
136
|
+
const result = await searchForExpressionProp(
|
|
137
|
+
entry.name,
|
|
138
|
+
actualPropName,
|
|
139
|
+
parentPath, // Use the path with the parent's variable name
|
|
140
|
+
searchText,
|
|
141
|
+
depth + 1,
|
|
142
|
+
)
|
|
143
|
+
if (result) return result
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Second, try to find spread prop usage: <Card {...cardProps} />
|
|
150
|
+
const spreadMatch = findSpreadProp(cached.ast, componentName)
|
|
151
|
+
|
|
152
|
+
if (spreadMatch) {
|
|
153
|
+
// Find the spread variable's definition
|
|
154
|
+
const spreadVarName = spreadMatch.spreadVarName
|
|
155
|
+
|
|
156
|
+
// The propName we're looking for should be a property of the spread object
|
|
157
|
+
// e.g., if propName is 'title' and spread is {...cardProps},
|
|
158
|
+
// we look for cardProps.title in the definitions
|
|
159
|
+
const spreadPropPath = `${spreadVarName}.${propName}`
|
|
160
|
+
|
|
161
|
+
for (const def of cached.variableDefinitions) {
|
|
162
|
+
const defPath = buildDefinitionPath(def)
|
|
163
|
+
if (defPath === spreadPropPath) {
|
|
164
|
+
const normalizedDef = normalizeText(def.value)
|
|
165
|
+
if (normalizedDef === normalizedSearch) {
|
|
166
|
+
return {
|
|
167
|
+
file: path.relative(getProjectRoot(), fullPath),
|
|
168
|
+
line: def.line,
|
|
169
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
170
|
+
type: 'variable',
|
|
171
|
+
variableName: defPath,
|
|
172
|
+
definitionLine: def.line,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check if the spread variable itself comes from props
|
|
179
|
+
if (cached.propAliases.has(spreadVarName)) {
|
|
180
|
+
const actualPropName = cached.propAliases.get(spreadVarName)!
|
|
181
|
+
// For spread from props, we need to search for the full path
|
|
182
|
+
const result = await searchForExpressionProp(
|
|
183
|
+
entry.name,
|
|
184
|
+
actualPropName,
|
|
185
|
+
expressionPath,
|
|
186
|
+
searchText,
|
|
187
|
+
depth + 1,
|
|
188
|
+
)
|
|
189
|
+
if (result) return result
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// Error reading directory
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return undefined
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Imported Value Search
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Search for a value in an imported file.
|
|
207
|
+
* @param fromFile - The file that contains the import
|
|
208
|
+
* @param importInfo - Information about the import
|
|
209
|
+
* @param expressionPath - The full expression path (e.g., 'config.title' or 'navItems[0]')
|
|
210
|
+
* @param searchText - The text content we're searching for
|
|
211
|
+
*/
|
|
212
|
+
export async function searchForImportedValue(
|
|
213
|
+
fromFile: string,
|
|
214
|
+
importInfo: ImportInfo,
|
|
215
|
+
expressionPath: string,
|
|
216
|
+
searchText: string,
|
|
217
|
+
): Promise<SourceLocation | undefined> {
|
|
218
|
+
// Resolve the import path to an absolute file path
|
|
219
|
+
const importedFilePath = await resolveImportPath(importInfo.source, fromFile)
|
|
220
|
+
if (!importedFilePath) return undefined
|
|
221
|
+
|
|
222
|
+
// Get exported definitions from the imported file
|
|
223
|
+
const exportedDefs = await getExportedDefinitions(importedFilePath)
|
|
224
|
+
if (exportedDefs.length === 0) return undefined
|
|
225
|
+
|
|
226
|
+
const normalizedSearch = normalizeText(searchText)
|
|
227
|
+
|
|
228
|
+
// Build the path we're looking for in the imported file
|
|
229
|
+
// e.g., if expressionPath is 'config.title' and localName is 'config',
|
|
230
|
+
// and importedName is 'siteConfig', we look for 'siteConfig.title'
|
|
231
|
+
let targetPath: string
|
|
232
|
+
if (importInfo.importedName === 'default' || importInfo.importedName === importInfo.localName) {
|
|
233
|
+
// Direct import: import { config } from './file' or import config from './file'
|
|
234
|
+
// The expression path uses the local name, which matches the exported name
|
|
235
|
+
targetPath = expressionPath
|
|
236
|
+
} else {
|
|
237
|
+
// Renamed import: import { config as siteConfig } from './file'
|
|
238
|
+
// Replace the local name with the original exported name
|
|
239
|
+
targetPath = expressionPath.replace(
|
|
240
|
+
new RegExp(`^${escapeRegex(importInfo.localName)}`),
|
|
241
|
+
importInfo.importedName,
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Search for the target path in the exported definitions
|
|
246
|
+
for (const def of exportedDefs) {
|
|
247
|
+
const defPath = buildDefinitionPath(def)
|
|
248
|
+
if (defPath === targetPath) {
|
|
249
|
+
const normalizedDef = normalizeText(def.value)
|
|
250
|
+
if (normalizedDef === normalizedSearch) {
|
|
251
|
+
const importedFileContent = await fs.readFile(importedFilePath, 'utf-8')
|
|
252
|
+
const importedLines = importedFileContent.split('\n')
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
file: path.relative(getProjectRoot(), importedFilePath),
|
|
256
|
+
line: def.line,
|
|
257
|
+
snippet: importedLines[def.line - 1] || '',
|
|
258
|
+
type: 'variable',
|
|
259
|
+
variableName: defPath,
|
|
260
|
+
definitionLine: def.line,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// Prop in Parents Search
|
|
271
|
+
// ============================================================================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Search for prop values passed to components using AST parsing.
|
|
275
|
+
* Uses caching for better performance.
|
|
276
|
+
*/
|
|
277
|
+
export async function searchForPropInParents(dir: string, textContent: string): Promise<SourceLocation | undefined> {
|
|
278
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
279
|
+
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
const fullPath = path.join(dir, entry.name)
|
|
282
|
+
|
|
283
|
+
if (entry.isDirectory()) {
|
|
284
|
+
const result = await searchForPropInParents(fullPath, textContent)
|
|
285
|
+
if (result) return result
|
|
286
|
+
} else if (entry.isFile() && entry.name.endsWith('.astro')) {
|
|
287
|
+
try {
|
|
288
|
+
// Use cached parsed file
|
|
289
|
+
const cached = await getCachedParsedFile(fullPath)
|
|
290
|
+
if (!cached) continue
|
|
291
|
+
|
|
292
|
+
const { lines, ast } = cached
|
|
293
|
+
|
|
294
|
+
// Find component props matching our text
|
|
295
|
+
const propMatch = findComponentProp(ast, textContent)
|
|
296
|
+
|
|
297
|
+
if (propMatch) {
|
|
298
|
+
// Extract component snippet for context
|
|
299
|
+
const componentStart = propMatch.line - 1
|
|
300
|
+
const snippetLines: string[] = []
|
|
301
|
+
let depth = 0
|
|
302
|
+
|
|
303
|
+
for (let i = componentStart; i < Math.min(componentStart + 10, lines.length); i++) {
|
|
304
|
+
const line = lines[i]
|
|
305
|
+
if (!line) continue
|
|
306
|
+
snippetLines.push(line)
|
|
307
|
+
|
|
308
|
+
// Check for self-closing or end of opening tag
|
|
309
|
+
if (line.includes('/>')) {
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
if (line.includes('>') && !line.includes('/>')) {
|
|
313
|
+
// Count opening tags
|
|
314
|
+
const opens = (line.match(/<[A-Z]/g) || []).length
|
|
315
|
+
const closes = (line.match(/\/>/g) || []).length
|
|
316
|
+
depth += opens - closes
|
|
317
|
+
if (depth <= 0 || (i > componentStart && line.includes('>'))) {
|
|
318
|
+
break
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
file: path.relative(getProjectRoot(), fullPath),
|
|
325
|
+
line: propMatch.line,
|
|
326
|
+
snippet: snippetLines.join('\n'),
|
|
327
|
+
type: 'prop',
|
|
328
|
+
variableName: propMatch.propName,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
// Error parsing file, continue
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return undefined
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Attribute Source Location Finding
|
|
342
|
+
// ============================================================================
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Find the actual source location for a dynamic attribute value.
|
|
346
|
+
* Uses the resolved VALUE to search for where it's defined (handles loop variables, etc.)
|
|
347
|
+
*
|
|
348
|
+
* @param expression - The source expression (e.g., "component.githubUrl")
|
|
349
|
+
* @param resolvedValue - The actual resolved value from the rendered HTML
|
|
350
|
+
* @param sourceFilePath - The source file path where the attribute is used (relative to project root)
|
|
351
|
+
* @returns Source location with file, line, and snippet for the actual value definition
|
|
352
|
+
*/
|
|
353
|
+
export async function findAttributeSourceLocation(
|
|
354
|
+
expression: string,
|
|
355
|
+
resolvedValue: string,
|
|
356
|
+
sourceFilePath: string,
|
|
357
|
+
): Promise<SourceLocation | undefined> {
|
|
358
|
+
// Parse the expression to get property name (e.g., "githubUrl" from "component.githubUrl")
|
|
359
|
+
const exprPath = parseExpressionPath(expression)
|
|
360
|
+
if (!exprPath) return undefined
|
|
361
|
+
|
|
362
|
+
// Get the property name (last part of the expression)
|
|
363
|
+
const propName = exprPath.includes('.') ? exprPath.split('.').pop()! : exprPath
|
|
364
|
+
|
|
365
|
+
const filePath = path.isAbsolute(sourceFilePath)
|
|
366
|
+
? sourceFilePath
|
|
367
|
+
: path.join(getProjectRoot(), sourceFilePath)
|
|
368
|
+
|
|
369
|
+
const cached = await getCachedParsedFile(filePath)
|
|
370
|
+
if (!cached) return undefined
|
|
371
|
+
|
|
372
|
+
// 1. Search local variable definitions by VALUE (handles loop variables)
|
|
373
|
+
// Look for definitions where: the property name matches AND the value matches
|
|
374
|
+
for (const def of cached.variableDefinitions) {
|
|
375
|
+
if (def.name === propName && def.value === resolvedValue) {
|
|
376
|
+
return {
|
|
377
|
+
file: path.relative(getProjectRoot(), filePath),
|
|
378
|
+
line: def.line,
|
|
379
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
380
|
+
type: 'variable',
|
|
381
|
+
variableName: buildDefinitionPath(def),
|
|
382
|
+
definitionLine: def.line,
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// 2. Search by exact expression path match
|
|
388
|
+
const baseVar = exprPath.match(/^(\w+)/)?.[1]
|
|
389
|
+
if (baseVar) {
|
|
390
|
+
for (const def of cached.variableDefinitions) {
|
|
391
|
+
const defPath = buildDefinitionPath(def)
|
|
392
|
+
if (defPath === exprPath && def.value === resolvedValue) {
|
|
393
|
+
return {
|
|
394
|
+
file: path.relative(getProjectRoot(), filePath),
|
|
395
|
+
line: def.line,
|
|
396
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
397
|
+
type: 'variable',
|
|
398
|
+
variableName: defPath,
|
|
399
|
+
definitionLine: def.line,
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 3. Check if the base variable comes from props
|
|
405
|
+
const actualPropName = cached.propAliases.get(baseVar)
|
|
406
|
+
if (actualPropName) {
|
|
407
|
+
const componentFileName = path.basename(filePath)
|
|
408
|
+
const result = await searchForExpressionPropAttributeByValue(
|
|
409
|
+
componentFileName,
|
|
410
|
+
propName,
|
|
411
|
+
resolvedValue,
|
|
412
|
+
)
|
|
413
|
+
if (result) return result
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 4. Check if the base variable comes from an import
|
|
417
|
+
const importInfo = cached.imports.find((imp) => imp.localName === baseVar)
|
|
418
|
+
if (importInfo) {
|
|
419
|
+
const result = await searchForImportedAttributeByValue(
|
|
420
|
+
filePath,
|
|
421
|
+
importInfo,
|
|
422
|
+
propName,
|
|
423
|
+
resolvedValue,
|
|
424
|
+
)
|
|
425
|
+
if (result) return result
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 5. Fallback: search all variable definitions by value only
|
|
430
|
+
for (const def of cached.variableDefinitions) {
|
|
431
|
+
if (def.value === resolvedValue) {
|
|
432
|
+
return {
|
|
433
|
+
file: path.relative(getProjectRoot(), filePath),
|
|
434
|
+
line: def.line,
|
|
435
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
436
|
+
type: 'variable',
|
|
437
|
+
variableName: buildDefinitionPath(def),
|
|
438
|
+
definitionLine: def.line,
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return undefined
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Search for attribute value in parent components by matching the resolved value.
|
|
448
|
+
*/
|
|
449
|
+
async function searchForExpressionPropAttributeByValue(
|
|
450
|
+
componentFileName: string,
|
|
451
|
+
propName: string,
|
|
452
|
+
resolvedValue: string,
|
|
453
|
+
depth: number = 0,
|
|
454
|
+
): Promise<SourceLocation | undefined> {
|
|
455
|
+
if (depth > 5) return undefined
|
|
456
|
+
|
|
457
|
+
const srcDir = path.join(getProjectRoot(), 'src')
|
|
458
|
+
const searchDirs = [
|
|
459
|
+
path.join(srcDir, 'pages'),
|
|
460
|
+
path.join(srcDir, 'components'),
|
|
461
|
+
path.join(srcDir, 'layouts'),
|
|
462
|
+
]
|
|
463
|
+
|
|
464
|
+
for (const dir of searchDirs) {
|
|
465
|
+
try {
|
|
466
|
+
const result = await searchDirForAttributeByValue(dir, propName, resolvedValue, depth)
|
|
467
|
+
if (result) return result
|
|
468
|
+
} catch {
|
|
469
|
+
// Directory doesn't exist
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return undefined
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function searchDirForAttributeByValue(
|
|
477
|
+
dir: string,
|
|
478
|
+
propName: string,
|
|
479
|
+
resolvedValue: string,
|
|
480
|
+
depth: number,
|
|
481
|
+
): Promise<SourceLocation | undefined> {
|
|
482
|
+
try {
|
|
483
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
484
|
+
|
|
485
|
+
for (const entry of entries) {
|
|
486
|
+
const fullPath = path.join(dir, entry.name)
|
|
487
|
+
|
|
488
|
+
if (entry.isDirectory()) {
|
|
489
|
+
const result = await searchDirForAttributeByValue(fullPath, propName, resolvedValue, depth)
|
|
490
|
+
if (result) return result
|
|
491
|
+
} else if (entry.isFile() && entry.name.endsWith('.astro')) {
|
|
492
|
+
const cached = await getCachedParsedFile(fullPath)
|
|
493
|
+
if (!cached) continue
|
|
494
|
+
|
|
495
|
+
// Search for variable definitions matching propName and value
|
|
496
|
+
for (const def of cached.variableDefinitions) {
|
|
497
|
+
if (def.name === propName && def.value === resolvedValue) {
|
|
498
|
+
return {
|
|
499
|
+
file: path.relative(getProjectRoot(), fullPath),
|
|
500
|
+
line: def.line,
|
|
501
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
502
|
+
type: 'variable',
|
|
503
|
+
variableName: buildDefinitionPath(def),
|
|
504
|
+
definitionLine: def.line,
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
// Error reading directory
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return undefined
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Search for attribute value in imported files by matching the resolved value.
|
|
519
|
+
*/
|
|
520
|
+
async function searchForImportedAttributeByValue(
|
|
521
|
+
fromFile: string,
|
|
522
|
+
importInfo: ImportInfo,
|
|
523
|
+
propName: string,
|
|
524
|
+
resolvedValue: string,
|
|
525
|
+
): Promise<SourceLocation | undefined> {
|
|
526
|
+
const importedFilePath = await resolveImportPath(importInfo.source, fromFile)
|
|
527
|
+
if (!importedFilePath) return undefined
|
|
528
|
+
|
|
529
|
+
const exportedDefs = await getExportedDefinitions(importedFilePath)
|
|
530
|
+
if (exportedDefs.length === 0) return undefined
|
|
531
|
+
|
|
532
|
+
// Search for definitions matching propName and value
|
|
533
|
+
for (const def of exportedDefs) {
|
|
534
|
+
if (def.name === propName && def.value === resolvedValue) {
|
|
535
|
+
const importedFileContent = await fs.readFile(importedFilePath, 'utf-8')
|
|
536
|
+
const importedLines = importedFileContent.split('\n')
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
file: path.relative(getProjectRoot(), importedFilePath),
|
|
540
|
+
line: def.line,
|
|
541
|
+
snippet: importedLines[def.line - 1] || '',
|
|
542
|
+
type: 'variable',
|
|
543
|
+
variableName: buildDefinitionPath(def),
|
|
544
|
+
definitionLine: def.line,
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Also try matching by value only as fallback
|
|
550
|
+
for (const def of exportedDefs) {
|
|
551
|
+
if (def.value === resolvedValue) {
|
|
552
|
+
const importedFileContent = await fs.readFile(importedFilePath, 'utf-8')
|
|
553
|
+
const importedLines = importedFileContent.split('\n')
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
file: path.relative(getProjectRoot(), importedFilePath),
|
|
557
|
+
line: def.line,
|
|
558
|
+
snippet: importedLines[def.line - 1] || '',
|
|
559
|
+
type: 'variable',
|
|
560
|
+
variableName: buildDefinitionPath(def),
|
|
561
|
+
definitionLine: def.line,
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return undefined
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Search for attribute value in parent components via expression props.
|
|
571
|
+
* @deprecated Use searchForExpressionPropAttributeByValue instead
|
|
572
|
+
*/
|
|
573
|
+
async function searchForExpressionPropAttribute(
|
|
574
|
+
componentFileName: string,
|
|
575
|
+
propName: string,
|
|
576
|
+
expressionPath: string,
|
|
577
|
+
depth: number = 0,
|
|
578
|
+
): Promise<SourceLocation | undefined> {
|
|
579
|
+
if (depth > 5) return undefined
|
|
580
|
+
|
|
581
|
+
const srcDir = path.join(getProjectRoot(), 'src')
|
|
582
|
+
const searchDirs = [
|
|
583
|
+
path.join(srcDir, 'pages'),
|
|
584
|
+
path.join(srcDir, 'components'),
|
|
585
|
+
path.join(srcDir, 'layouts'),
|
|
586
|
+
]
|
|
587
|
+
|
|
588
|
+
const componentName = path.basename(componentFileName, '.astro')
|
|
589
|
+
|
|
590
|
+
for (const dir of searchDirs) {
|
|
591
|
+
try {
|
|
592
|
+
const result = await searchDirForAttributeProp(
|
|
593
|
+
dir,
|
|
594
|
+
componentName,
|
|
595
|
+
propName,
|
|
596
|
+
expressionPath,
|
|
597
|
+
depth,
|
|
598
|
+
)
|
|
599
|
+
if (result) return result
|
|
600
|
+
} catch {
|
|
601
|
+
// Directory doesn't exist
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return undefined
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function searchDirForAttributeProp(
|
|
609
|
+
dir: string,
|
|
610
|
+
componentName: string,
|
|
611
|
+
propName: string,
|
|
612
|
+
expressionPath: string,
|
|
613
|
+
depth: number,
|
|
614
|
+
): Promise<SourceLocation | undefined> {
|
|
615
|
+
try {
|
|
616
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
617
|
+
|
|
618
|
+
for (const entry of entries) {
|
|
619
|
+
const fullPath = path.join(dir, entry.name)
|
|
620
|
+
|
|
621
|
+
if (entry.isDirectory()) {
|
|
622
|
+
const result = await searchDirForAttributeProp(
|
|
623
|
+
fullPath,
|
|
624
|
+
componentName,
|
|
625
|
+
propName,
|
|
626
|
+
expressionPath,
|
|
627
|
+
depth,
|
|
628
|
+
)
|
|
629
|
+
if (result) return result
|
|
630
|
+
} else if (entry.isFile() && entry.name.endsWith('.astro')) {
|
|
631
|
+
const cached = await getCachedParsedFile(fullPath)
|
|
632
|
+
if (!cached) continue
|
|
633
|
+
|
|
634
|
+
// Find expression prop usage: <Component prop={variable} />
|
|
635
|
+
const exprPropMatch = findExpressionProp(cached.ast, componentName, propName)
|
|
636
|
+
|
|
637
|
+
if (exprPropMatch) {
|
|
638
|
+
const exprText = exprPropMatch.expressionText
|
|
639
|
+
// Build the path in the parent's context
|
|
640
|
+
const parentPath = expressionPath.replace(/^[^.[]+/, exprText)
|
|
641
|
+
|
|
642
|
+
// Check local variable definitions
|
|
643
|
+
for (const def of cached.variableDefinitions) {
|
|
644
|
+
const defPath = buildDefinitionPath(def)
|
|
645
|
+
if (defPath === parentPath) {
|
|
646
|
+
return {
|
|
647
|
+
file: path.relative(getProjectRoot(), fullPath),
|
|
648
|
+
line: def.line,
|
|
649
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
650
|
+
type: 'variable',
|
|
651
|
+
variableName: defPath,
|
|
652
|
+
definitionLine: def.line,
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Check if exprText is from props (multi-level drilling)
|
|
658
|
+
const baseVar = exprText.match(/^(\w+)/)?.[1]
|
|
659
|
+
if (baseVar && cached.propAliases.has(baseVar)) {
|
|
660
|
+
const actualPropName = cached.propAliases.get(baseVar)!
|
|
661
|
+
const result = await searchForExpressionPropAttribute(
|
|
662
|
+
entry.name,
|
|
663
|
+
actualPropName,
|
|
664
|
+
parentPath,
|
|
665
|
+
depth + 1,
|
|
666
|
+
)
|
|
667
|
+
if (result) return result
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Try spread prop usage
|
|
672
|
+
const spreadMatch = findSpreadProp(cached.ast, componentName)
|
|
673
|
+
if (spreadMatch) {
|
|
674
|
+
const spreadPropPath = `${spreadMatch.spreadVarName}.${propName}`
|
|
675
|
+
for (const def of cached.variableDefinitions) {
|
|
676
|
+
const defPath = buildDefinitionPath(def)
|
|
677
|
+
if (defPath === spreadPropPath) {
|
|
678
|
+
return {
|
|
679
|
+
file: path.relative(getProjectRoot(), fullPath),
|
|
680
|
+
line: def.line,
|
|
681
|
+
snippet: cached.lines[def.line - 1] || '',
|
|
682
|
+
type: 'variable',
|
|
683
|
+
variableName: defPath,
|
|
684
|
+
definitionLine: def.line,
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
// Error reading directory
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return undefined
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Search for attribute value in an imported file.
|
|
700
|
+
*/
|
|
701
|
+
async function searchForImportedAttribute(
|
|
702
|
+
fromFile: string,
|
|
703
|
+
importInfo: ImportInfo,
|
|
704
|
+
expressionPath: string,
|
|
705
|
+
): Promise<SourceLocation | undefined> {
|
|
706
|
+
const importedFilePath = await resolveImportPath(importInfo.source, fromFile)
|
|
707
|
+
if (!importedFilePath) return undefined
|
|
708
|
+
|
|
709
|
+
const exportedDefs = await getExportedDefinitions(importedFilePath)
|
|
710
|
+
if (exportedDefs.length === 0) return undefined
|
|
711
|
+
|
|
712
|
+
// Build the target path in the imported file
|
|
713
|
+
let targetPath: string
|
|
714
|
+
if (importInfo.importedName === 'default' || importInfo.importedName === importInfo.localName) {
|
|
715
|
+
targetPath = expressionPath
|
|
716
|
+
} else {
|
|
717
|
+
targetPath = expressionPath.replace(
|
|
718
|
+
new RegExp(`^${escapeRegex(importInfo.localName)}`),
|
|
719
|
+
importInfo.importedName,
|
|
720
|
+
)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
for (const def of exportedDefs) {
|
|
724
|
+
const defPath = buildDefinitionPath(def)
|
|
725
|
+
if (defPath === targetPath) {
|
|
726
|
+
const importedFileContent = await fs.readFile(importedFilePath, 'utf-8')
|
|
727
|
+
const importedLines = importedFileContent.split('\n')
|
|
728
|
+
|
|
729
|
+
return {
|
|
730
|
+
file: path.relative(getProjectRoot(), importedFilePath),
|
|
731
|
+
line: def.line,
|
|
732
|
+
snippet: importedLines[def.line - 1] || '',
|
|
733
|
+
type: 'variable',
|
|
734
|
+
variableName: defPath,
|
|
735
|
+
definitionLine: def.line,
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return undefined
|
|
741
|
+
}
|