Skip to content

Commit

Permalink
Navigate to the annotation in the sidecar file (#5999)
Browse files Browse the repository at this point in the history
**Problem:**
Followup to #5989
Clicking on the Components inspector section's ◇ logo opens the
annotation file for the given component AND jumps to the annotation of
the component in the file.

(Second task of #5971 )


**Fix:**
I used the Babel parser to find the component annotation in the sidecar
file.
- It can find the annotation if it in the exported object literal, or
even if it is in a variable referred from this file. It does not support
imports though, everything has to be in this file.
- This is a best effort to find the annotation, if it can not do it like
this (e.g. the annotation is dynamically generated, or anything which
the parser is not prepared for), then it just doesn't save the bounds.
Clicking on the button will still open the sidecar file.

The bounds are stored in the component descriptor source object (which
until now only stored the file path itself).

**Manual Tests:**
I hereby swear that:

- [x] I opened a hydrogen project and it loaded
- [x] I could navigate to various routes in Preview mode
  • Loading branch information
gbalint authored and liady committed Dec 13, 2024
1 parent 51b66c3 commit 779d07e
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 29 deletions.
4 changes: 4 additions & 0 deletions editor/src/components/custom-code/code-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { getProjectFileByFilePath } from '../assets'
import type { EditorDispatch } from '../editor/action-types'
import { StylingOptions } from 'utopia-api'
import type { Emphasis, Focus, Icon, Styling } from 'utopia-api'
import type { Bounds } from 'utopia-vscode-common'

type ModuleExportTypes = { [name: string]: ExportType }

Expand Down Expand Up @@ -227,14 +228,17 @@ export function isDefaultComponentDescriptor(
export interface ComponentDescriptorFromDescriptorFile {
type: 'DESCRIPTOR_FILE'
sourceDescriptorFile: string
bounds: Bounds | null
}

export function componentDescriptorFromDescriptorFile(
sourceDescriptorFile: string,
bounds: Bounds | null,
): ComponentDescriptorFromDescriptorFile {
return {
type: 'DESCRIPTOR_FILE',
sourceDescriptorFile: sourceDescriptorFile,
bounds: bounds,
}
}

Expand Down
2 changes: 2 additions & 0 deletions editor/src/components/editor/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import type { MapLike } from 'typescript'
import type { CommentFilterMode } from '../inspector/sections/comment-section'
import type { Collaborator } from '../../core/shared/multiplayer'
import type { PageTemplate } from '../canvas/remix/remix-utils'
import type { Bounds } from 'utopia-vscode-common'
export { isLoggedIn, loggedInUser, notLoggedIn } from '../../common/user'
export type { LoginState, UserDetails } from '../../common/user'

Expand Down Expand Up @@ -644,6 +645,7 @@ export interface OpenCodeEditorFile {
action: 'OPEN_CODE_EDITOR_FILE'
filename: string
forceShowCodeEditor: boolean
bounds: Bounds | null
}

export interface CloseDesignerFile {
Expand Down
3 changes: 3 additions & 0 deletions editor/src/components/editor/actions/action-creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ import type { SetHuggingParentToFixed } from '../../canvas/canvas-strategies/str
import type { CommentFilterMode } from '../../inspector/sections/comment-section'
import type { Collaborator } from '../../../core/shared/multiplayer'
import type { PageTemplate } from '../../canvas/remix/remix-utils'
import type { Bounds } from 'utopia-vscode-common'

export function clearSelection(): EditorAction {
return {
Expand Down Expand Up @@ -1055,11 +1056,13 @@ export function addFolder(parentPath: string, fileName: string): AddFolder {
export function openCodeEditorFile(
filename: string,
forceShowCodeEditor: boolean,
bounds: Bounds | null = null,
): OpenCodeEditorFile {
return {
action: 'OPEN_CODE_EDITOR_FILE',
filename: filename,
forceShowCodeEditor: forceShowCodeEditor,
bounds: bounds,
}
}

Expand Down
10 changes: 5 additions & 5 deletions editor/src/components/editor/actions/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ import {
import { loadStoredState } from '../stored-state'
import { applyMigrations } from './migrations/migrations'

import { defaultConfig } from 'utopia-vscode-common'
import { boundsInFile, defaultConfig } from 'utopia-vscode-common'
import { reorderElement } from '../../../components/canvas/commands/reorder-element-command'
import type { BuiltInDependencies } from '../../../core/es-modules/package-manager/built-in-dependencies-list'
import { fetchNodeModules } from '../../../core/es-modules/package-manager/fetch-packages'
Expand Down Expand Up @@ -593,7 +593,7 @@ import {
getPrintAndReparseCodeResult,
} from '../../../core/workers/parser-printer/parser-printer-worker'
import { isSteganographyEnabled } from '../../../core/shared/stegano-text'
import type { ParsedTextFileWithPath } from '../../../core/property-controls/property-controls-local'
import type { TextFileContentsWithPath } from '../../../core/property-controls/property-controls-local'
import {
updatePropertyControlsOnDescriptorFileDelete,
isComponentDescriptorFile,
Expand Down Expand Up @@ -3795,7 +3795,7 @@ export const UPDATE_FNS = {
},
OPEN_CODE_EDITOR_FILE: (action: OpenCodeEditorFile, editor: EditorModel): EditorModel => {
// Side effect.
sendOpenFileMessage(action.filename)
sendOpenFileMessage(action.filename, action.bounds)
if (action.forceShowCodeEditor) {
return {
...editor,
Expand Down Expand Up @@ -6073,11 +6073,11 @@ export const UPDATE_FNS = {
): EditorModel => {
const evaluator = createModuleEvaluator(state)

const filesToUpdate: ParsedTextFileWithPath[] = []
const filesToUpdate: TextFileContentsWithPath[] = []
for (const filePath of action.paths) {
const file = getProjectFileByFilePath(state.projectContents, filePath)
if (file != null && file.type === 'TEXT_FILE') {
filesToUpdate.push({ path: filePath, file: file.fileContents.parsed })
filesToUpdate.push({ path: filePath, file: file.fileContents })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ import type {
import { fontSettings } from '../../inspector/common/css-utils'
import type { ElementPaste, ProjectListing } from '../action-types'
import { projectListing } from '../action-types'
import type { UtopiaVSCodeConfig } from 'utopia-vscode-common'
import type { Bounds, UtopiaVSCodeConfig } from 'utopia-vscode-common'
import type { MouseButtonsPressed } from '../../../utils/mouse'
import { assertNever } from '../../../core/shared/utils'
import type {
Expand Down Expand Up @@ -3129,6 +3129,23 @@ export const HighlightBoundsKeepDeepEquality: KeepDeepEqualityCall<HighlightBoun
highlightBounds,
)

export const BoundsKeepDeepEquality: KeepDeepEqualityCall<Bounds> = combine4EqualityCalls(
(bounds) => bounds.startLine,
NumberKeepDeepEquality,
(bounds) => bounds.startCol,
NumberKeepDeepEquality,
(bounds) => bounds.endLine,
NumberKeepDeepEquality,
(bounds) => bounds.endCol,
NumberKeepDeepEquality,
(startLine, startCol, endLine, endCol) => ({
startLine,
startCol,
endLine,
endCol,
}),
)

export const HighlightBoundsForUidsKeepDeepEquality: KeepDeepEqualityCall<HighlightBoundsForUids> =
objectDeepEquality(HighlightBoundsKeepDeepEquality)

Expand Down Expand Up @@ -3484,12 +3501,15 @@ const PreferredChildComponentDescriptorKeepDeepEquality: KeepDeepEqualityCall<Pr
)

export const DescriptorFileComponentDescriptorKeepDeepEquality: KeepDeepEqualityCall<ComponentDescriptorFromDescriptorFile> =
combine2EqualityCalls(
combine3EqualityCalls(
(descriptor) => descriptor.type,
StringKeepDeepEquality,
(descriptor) => descriptor.sourceDescriptorFile,
StringKeepDeepEquality,
componentDescriptorFromDescriptorFile,
(descriptor) => descriptor.bounds,
nullableDeepEquality(BoundsKeepDeepEquality),
(_, sourceDescriptorFile, lineNumber) =>
componentDescriptorFromDescriptorFile(sourceDescriptorFile, lineNumber),
)

export function ComponentDescriptorSourceKeepDeepEquality(): KeepDeepEqualityCall<ComponentDescriptorSource> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1469,9 +1469,7 @@ export const ComponentSectionInner = React.memo((props: ComponentSectionProps) =
)

const descriptorFile =
registeredComponent?.source.type === 'DESCRIPTOR_FILE'
? registeredComponent?.source.sourceDescriptorFile
: null
registeredComponent?.source.type === 'DESCRIPTOR_FILE' ? registeredComponent.source : null

if (registeredComponent?.label == null) {
return {
Expand All @@ -1493,7 +1491,13 @@ export const ComponentSectionInner = React.memo((props: ComponentSectionProps) =

const openDescriptorFile = React.useCallback(() => {
if (componentData?.descriptorFile != null) {
dispatch([openCodeEditorFile(componentData?.descriptorFile, true)])
dispatch([
openCodeEditorFile(
componentData.descriptorFile.sourceDescriptorFile,
true,
componentData.descriptorFile.bounds,
),
])
}
}, [dispatch, componentData?.descriptorFile])

Expand Down
89 changes: 89 additions & 0 deletions editor/src/core/property-controls/component-descriptor-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as BabelParser from '@babel/parser'
import traverse from '@babel/traverse'
import type { Bounds } from 'utopia-vscode-common'
import type { TextFileContentsWithPath } from './property-controls-local'

export type ComponentBoundsByModule = {
[moduleName: string]: { [componentName: string]: Bounds }
}

export function generateComponentBounds(
descriptorFile: TextFileContentsWithPath,
): ComponentBoundsByModule {
const ast = BabelParser.parse(descriptorFile.file.code, {
sourceType: 'module',
plugins: ['jsx'],
})

const componentBoundsByModule: ComponentBoundsByModule = {}

// Store variable declarations
const variableDeclarations: Record<string, any> = {}
// Traverse the AST to collect variable declarations
traverse(ast, {
VariableDeclarator(path) {
const { id, init } = path.node
if (id.type === 'Identifier' && init != null) {
variableDeclarations[id.name] = init
}
},
})

// Function to resolve a variable to its object value
function resolveVariable(node: any): any {
if (node.type === 'Identifier' && variableDeclarations[node.name] != null) {
return variableDeclarations[node.name]
}
return node
}

// Traverse the AST to find the default export and process the Components object
traverse(ast, {
ExportDefaultDeclaration(path) {
const declaration = path.node.declaration
if (declaration.type !== 'Identifier') {
return
}

const variableName = declaration.name
const componentsNode = variableDeclarations[variableName]
if (componentsNode?.type !== 'ObjectExpression') {
return
}

componentsNode.properties.forEach((module: any) => {
if (module.type !== 'ObjectProperty' || module.key.type !== 'StringLiteral') {
return
}

const moduleName = module.key.value
if (componentBoundsByModule[moduleName] == null) {
componentBoundsByModule[moduleName] = {}
}

const moduleValue = resolveVariable(module.value)
if (moduleValue.type !== 'ObjectExpression') {
return
}

moduleValue.properties.forEach((component: any) => {
if (component.type !== 'ObjectProperty' || component.key.type !== 'Identifier') {
return
}

const componentName = component.key.name
const { loc } = component
if (loc != null) {
componentBoundsByModule[moduleName][componentName] = {
startLine: loc.start.line,
startCol: loc.start.column,
endLine: loc.end.line,
endCol: loc.end.column,
}
}
})
})
},
})
return componentBoundsByModule
}
49 changes: 49 additions & 0 deletions editor/src/core/property-controls/property-controls-local.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ describe('registered property controls', () => {
},
},
"source": Object {
"bounds": Object {
"endCol": 5,
"endLine": 42,
"startCol": 4,
"startLine": 5,
},
"sourceDescriptorFile": "/utopia/components.utopia.js",
"type": "DESCRIPTOR_FILE",
},
Expand Down Expand Up @@ -287,6 +293,7 @@ describe('registered property controls', () => {
},
},
"source": Object {
"bounds": null,
"sourceDescriptorFile": "/utopia/components.utopia.js",
"type": "DESCRIPTOR_FILE",
},
Expand Down Expand Up @@ -370,6 +377,12 @@ describe('registered property controls', () => {
},
},
"source": Object {
"bounds": Object {
"endCol": 5,
"endLine": 16,
"startCol": 4,
"startLine": 6,
},
"sourceDescriptorFile": "/utopia/components.utopia.js",
"type": "DESCRIPTOR_FILE",
},
Expand Down Expand Up @@ -997,6 +1010,12 @@ describe('registered property controls', () => {
},
},
"source": Object {
"bounds": Object {
"endCol": 5,
"endLine": 32,
"startCol": 4,
"startLine": 5,
},
"sourceDescriptorFile": "/utopia/components.utopia.js",
"type": "DESCRIPTOR_FILE",
},
Expand Down Expand Up @@ -1911,6 +1930,12 @@ describe('registered property controls', () => {
},
},
"source": Object {
"bounds": Object {
"endCol": 5,
"endLine": 34,
"startCol": 4,
"startLine": 6,
},
"sourceDescriptorFile": "/utopia/components.utopia.js",
"type": "DESCRIPTOR_FILE",
},
Expand Down Expand Up @@ -2662,6 +2687,12 @@ describe('Lifecycle management of registering components', () => {
expect(renderResult.getEditorState().editor.propertyControlsInfo['/src/card']['Card'].source)
.toMatchInlineSnapshot(`
Object {
"bounds": Object {
"endCol": 5,
"endLine": 12,
"startCol": 4,
"startLine": 5,
},
"sourceDescriptorFile": "/utopia/components1.utopia.js",
"type": "DESCRIPTOR_FILE",
}
Expand All @@ -2673,6 +2704,12 @@ describe('Lifecycle management of registering components', () => {
expect(renderResult.getEditorState().editor.propertyControlsInfo['/src/card']['Card'].source)
.toMatchInlineSnapshot(`
Object {
"bounds": Object {
"endCol": 5,
"endLine": 12,
"startCol": 4,
"startLine": 5,
},
"sourceDescriptorFile": "/utopia/components2.utopia.js",
"type": "DESCRIPTOR_FILE",
}
Expand Down Expand Up @@ -2708,6 +2745,12 @@ describe('Lifecycle management of registering components', () => {
expect(renderResult.getEditorState().editor.propertyControlsInfo['/src/card']['Card'].source)
.toMatchInlineSnapshot(`
Object {
"bounds": Object {
"endCol": 5,
"endLine": 12,
"startCol": 4,
"startLine": 5,
},
"sourceDescriptorFile": "/utopia/components1.utopia.js",
"type": "DESCRIPTOR_FILE",
}
Expand Down Expand Up @@ -2753,6 +2796,12 @@ describe('Lifecycle management of registering components', () => {
expect(renderResult.getEditorState().editor.propertyControlsInfo['/src/card']['Card'].source)
.toMatchInlineSnapshot(`
Object {
"bounds": Object {
"endCol": 5,
"endLine": 12,
"startCol": 4,
"startLine": 5,
},
"sourceDescriptorFile": "/utopia/components1.utopia.js",
"type": "DESCRIPTOR_FILE",
}
Expand Down
Loading

0 comments on commit 779d07e

Please sign in to comment.