Skip to content

Commit

Permalink
Cleanup autoname plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
thetarnav committed Dec 11, 2024
1 parent 92ba862 commit 7569ff3
Showing 1 changed file with 113 additions and 119 deletions.
232 changes: 113 additions & 119 deletions packages/main/src/babel/name.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {type PluginObj} from '@babel/core'
import * as t from '@babel/types'

const nameId = t.identifier('name')
const undefinedId = t.identifier('undefined')
const NAME_ID = t.identifier('name')
const UNDEFINED_ID = t.identifier('undefined')

type Comparable = t.Identifier | t.V8IntrinsicIdentifier | t.PrivateName | t.Expression
function equal(a: Comparable, b: Comparable): boolean {
Expand All @@ -23,36 +23,39 @@ function equal(a: Comparable, b: Comparable): boolean {
}

function addNameToOptions(
node: t.CallExpression,
argIndex: number,
getNameProperty: () => t.ObjectProperty | undefined,
node: t.CallExpression,
arg_idx: number,
name: string,
): void {

let name_property = t.objectProperty(NAME_ID, t.stringLiteral(name))

// fill in-between arguments with undefined
while (node.arguments.length < argIndex) {
node.arguments.push(undefinedId)
while (node.arguments.length < arg_idx) {
node.arguments.push(UNDEFINED_ID)
}

// no options argument
if (node.arguments.length === arg_idx) {
node.arguments.push(t.objectExpression([name_property]))
}
if (node.arguments.length === argIndex) {
// no options argument
const nameProperty = getNameProperty()
nameProperty && node.arguments.push(t.objectExpression([nameProperty]))
} else {
else {
// existing options argument
const options = node.arguments[argIndex]!
if (options.type !== 'ObjectExpression') return
const options = node.arguments[arg_idx]!
if (options.type !== 'ObjectExpression') {
return
}

// Check there isn't already a "name" property
if (
options.properties.some(
property =>
property.type === 'ObjectProperty' &&
property.key.type === 'Identifier' &&
property.key.name === nameId.name,
)
) {
if (options.properties.some(prop =>
prop.type === 'ObjectProperty' &&
prop.key.type === 'Identifier' &&
prop.key.name === NAME_ID.name,
)) {
return
}

const nameProperty = getNameProperty()
nameProperty && options.properties.unshift(nameProperty)
options.properties.unshift(name_property)
}
}

Expand All @@ -65,9 +68,9 @@ type Source =
| 'createRenderEffect'
| 'createComputed'

const SOURCE_TYPES: Record<'returning' | 'effect', ReadonlySet<Source>> = {
const SOURCE_TYPES = {
returning: new Set<Source>(['createSignal', 'createMemo', 'createStore', 'createMutable']),
effect: new Set<Source>(['createEffect', 'createRenderEffect', 'createComputed']),
effect: new Set<Source>(['createEffect', 'createRenderEffect', 'createComputed']),
}

const SOURCE_MODULES: Record<string, ReadonlySet<Source>> = {
Expand All @@ -81,151 +84,142 @@ const SOURCE_MODULES: Record<string, ReadonlySet<Source>> = {
'solid-js/store': new Set<Source>(['createStore', 'createMutable']),
}

const OPTIONS_ARG: Record<Source, number> = {
createSignal: 1,
createMemo: 2,
createStore: 1,
createMutable: 1,
createEffect: 2,
const OPTIONS_ARG_POS: Record<Source, number> = {
createSignal : 1,
createMemo : 2,
createStore : 1,
createMutable : 1,
createEffect : 2,
createRenderEffect: 2,
createComputed: 2,
createComputed : 2,
}

function getTarget(
node: Comparable,
sources: typeof Sources,
includedOptions: ReadonlySet<Source>,
): Source | undefined {
return Object.entries(sources).find(
([sourcesKey, someSources]) =>
includedOptions.has(sourcesKey as Source) &&
someSources.some(source => equal(node, source)),
return Object.entries(sources).find(([sourcesKey, someSources]) =>
includedOptions.has(sourcesKey) &&
someSources.some(source => equal(node, source))
)?.[0] as Source | undefined
}

let Sources: Record<Source, Comparable[]>
let FileWithImports: boolean = false

export const namePlugin: PluginObj<any> = {
name: '@solid-devtools/name',
name: '@solid-devtools/autoname',
visitor: {
Program() {
Sources = {
createSignal: [],
createMemo: [],
createStore: [],
createMutable: [],
createEffect: [],
createSignal : [],
createMemo : [],
createStore : [],
createMutable : [],
createEffect : [],
createRenderEffect: [],
createComputed: [],
createComputed : [],
}
FileWithImports = false
},

// Track imported references to sources
ImportDeclaration(path) {
const node = path.node
const source = node.source.value

const source = path.node.source.value
const targets = SOURCE_MODULES[source]
if (!targets) return

for (const s of node.specifiers) {
for (const s of path.node.specifiers) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (s.type) {
// import * as local from "solid-js"
case 'ImportNamespaceSpecifier':
for (const target of targets) {
Sources[target].push(t.memberExpression(s.local, t.identifier(target)))
FileWithImports = true
}
break
// import { createSignal } from "solid-js"
// import { createSignal as local } from "solid-js"
// import { "createSignal" as local } from "solid-js"
case 'ImportSpecifier': {
const name =
s.imported.type === 'Identifier' ? s.imported.name : s.imported.value
if (targets.has(name as Source)) {
Sources[name as Source].push(s.local)
FileWithImports = true
}
break
// import * as local from "solid-js"
case 'ImportNamespaceSpecifier':
for (const target of targets) {
Sources[target].push(t.memberExpression(s.local, t.identifier(target)))
FileWithImports = true
}
break
// import { createSignal } from "solid-js"
// import { createSignal as local } from "solid-js"
// import { "createSignal" as local } from "solid-js"
case 'ImportSpecifier': {
const name = s.imported.type === 'Identifier' ? s.imported.name : s.imported.value
if (targets.has(name)) {
Sources[name as Source].push(s.local)
FileWithImports = true
}
break
}
}
}
},

VariableDeclaration(path) {
if (!FileWithImports) return

const declarations = path.node.declarations
for (const declaration of declarations) {
for (const decl of path.node.declarations) {

// Check initializer is a call to createSignal/createMemo/createStore/createMutable
const init = declaration.init
if (!init || init.type !== 'CallExpression') continue
if (!decl.init || decl.init.type !== 'CallExpression') continue

const target = getTarget(init.callee, Sources, SOURCE_TYPES.returning)
const target = getTarget(decl.init.callee, Sources, SOURCE_TYPES.returning)
if (!target) continue

// Modify call to include name in options
addNameToOptions(init, OPTIONS_ARG[target], () => {
// Check declaration is either identifier or [identifier, ...]
const id = declaration.id
const name = (() => {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (id.type) {
case 'Identifier':
return id.name
case 'ArrayPattern': {
if (!id.elements.length) return
const first = id.elements[0]
if (!first) return
if (first.type !== 'Identifier') return
return first.name
}
}
})()

return name ? t.objectProperty(nameId, t.stringLiteral(name)) : undefined
})
let name: string

// Check declaration is either identifier or [identifier, ...]
if (decl.id.type === 'Identifier') {
name = decl.id.name
}
else if (decl.id.type === 'ArrayPattern' && decl.id.elements[0]?.type === 'Identifier') {
name = decl.id.elements[0].name
}
else {
continue
}

addNameToOptions(decl.init, OPTIONS_ARG_POS[target], name)
}
},

CallExpression(path) {
if (!FileWithImports) return

const node = path.node
const target = getTarget(node.callee, Sources, SOURCE_TYPES.effect)
const target = getTarget(path.node.callee, Sources, SOURCE_TYPES.effect)
if (!target) return

// Modify call to include name in options
addNameToOptions(node, 2, () => {
let name: string | undefined

let parentPath = path.parentPath as typeof path.parentPath | null
for (let i = 0; i < 5; i++) {
if (!parentPath) return
if (
parentPath.node.type === 'CallExpression' &&
parentPath.node.callee.type === 'Identifier'
) {
name = `to_${parentPath.node.callee.name}`
break
}
if (
(parentPath.node.type === 'FunctionDeclaration' ||
parentPath.node.type === 'VariableDeclarator') &&
parentPath.node.id?.type === 'Identifier'
) {
name = `in_${parentPath.node.id.name}`
break
}
parentPath = parentPath.parentPath
let name: string | undefined

let parent = path.parentPath
for (let i = 0; i < 5; i++) {

if (parent.node.type === 'CallExpression' &&
parent.node.callee.type === 'Identifier'
) {
name = `to_${parent.node.callee.name}`
break
}
if (!name) return

return t.objectProperty(nameId, t.stringLiteral(name))
})
if ((parent.node.type === 'FunctionDeclaration' ||
parent.node.type === 'VariableDeclarator') &&
parent.node.id?.type === 'Identifier'
) {
name = `in_${parent.node.id.name}`
break
}

if (parent.parentPath) {
parent = parent.parentPath
} else {
break
}
}

if (name) {
addNameToOptions(path.node, 2, name)
}
},
},
}

0 comments on commit 7569ff3

Please sign in to comment.