Skip to content

Commit

Permalink
feat: Add skeleton for template modding API
Browse files Browse the repository at this point in the history
  • Loading branch information
Sidnioulz committed Aug 26, 2023
1 parent 847169c commit 8da1203
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 3 deletions.
78 changes: 77 additions & 1 deletion src/template/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import type {
AttributeNode,
BaseElementNode,
DirectiveNode,
Node,
RootNode,
SourceLocation,
TextNode,
} from '@vue/compiler-core'
import { NodeTypes } from '@vue/compiler-core'

import { isAttribute, isDirective, isText } from '~/template/utils'
import { isAttribute, isDirective, isNode, isText } from '~/template/utils'
import { debugTemplate } from '~/utils/debug'
import error from '~/utils/error'

Expand Down Expand Up @@ -103,6 +105,41 @@ export function compareAttributeValues(a: AttributeNode['value'], b: AttributeNo
return aContent === bContent
}

/* -- AST EXPLORATION FUNCTIONS -- */
export function exploreAst(ast: RootNode, matcher: (node: Node) => boolean): Node[] {
const nodeset = new Set<Node>()
const queue: Node[] = [ast]

while (queue.length) {
const currentNode = queue.pop()
if (currentNode) {
if (matcher(currentNode)) {
nodeset.add(currentNode)
}

if (typeof currentNode !== 'string') {
const nextNodes: unknown[] = Object.values(currentNode)
.filter(Boolean)
.reduce((acc, current) => {
if (Array.isArray(current)) {
return [...acc, ...current]
}

return [...acc, current]
}, [])

queue.push(...nextNodes.filter(isNode))
}
}
}

return Array.from(nodeset)
}

export function findAstAttributes(ast: RootNode, matcher?: (node: Node) => boolean) {
return exploreAst(ast, (node) => isAttribute(node) && (matcher?.(node) ?? true))
}

/* -- FIND FUNCTIONS -- */
export function findAttributes(
node: BaseElementNode,
Expand Down Expand Up @@ -156,6 +193,45 @@ export function findDirectives(
}) as DirectiveNode[]
}

/* -- UPDATE FUNCTIONS -- */
export function updateAttribute(
prop: AttributeNode,
updater: (attr: AttributeNode) => Partial<Omit<Omit<AttributeNode, 'loc'>, 'type'>>,
) {
/* eslint-disable no-param-reassign */
const changes = updater(prop)

if (Object.hasOwn(changes, 'name')) {
if (!changes.name) {
throw error('updateAttribute: Invalid changes to attribute name', { prop, changes })
}
prop.name = changes.name
}

if (Object.hasOwn(changes, 'value')) {
if (
changes.value !== undefined &&
typeof changes.value !== 'string' &&
!isText(changes.value)
) {
throw error('updateAttribute: Invalid changes to attribute value', { prop, changes })
}

if (changes.value === undefined) {
prop.value = undefined
} else if (isText(changes.value)) {
prop.value = changes.value
} else {
prop.value = createText({ content: changes.value })
}
}

prop.loc = genFakeLoc()

/* eslint-enable no-param-reassign */
return null
}

/* -- REMOVE FUNCTIONS -- */
export function removeAttribute(
node: BaseElementNode,
Expand Down
11 changes: 11 additions & 0 deletions src/template/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ export function isJsProperty(node: Node): node is Property {
export function isObjectExpression(node: Node): node is ObjectExpression {
return node.type === NodeTypes.JS_OBJECT_EXPRESSION
}
export function isNode(node: unknown): node is Node {
return (
typeof node === 'object' &&
!!node &&
// Typescript in all its splendor. Object.hasOwn is unsupported.
'type' in node &&
typeof node.type === 'number' &&
node.type >= 0 &&
node.type <= 26
)
}
export function isRoot(node: Node): node is RootNode {
return node.type === NodeTypes.ROOT
}
Expand Down
3 changes: 2 additions & 1 deletion src/transformTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { compileTemplate } from '@vue/compiler-sfc'

import processTransformResult from '~/processTransformResult'
import * as TemplateAPI from '~/template/api'
import { stringify } from '~/template/stringify'
import type { TemplateTransformation } from '~/types/TemplateTransformation'
import type { TransformationBlock } from '~/types/TransformationBlock'
Expand All @@ -27,7 +28,7 @@ export default function transformTemplate(
)
}

const out = stringify(transformation(result.ast))
const out = stringify(transformation(result.ast, TemplateAPI))

return processTransformResult(descriptor, out)
}
4 changes: 3 additions & 1 deletion src/types/TemplateTransformation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { RootNode } from '@vue/compiler-core'

export type TemplateTransformation = (ast: RootNode) => RootNode
import * as TemplateAPI from '~/template/api'

export type TemplateTransformation = (ast: RootNode, api: typeof TemplateAPI) => RootNode

0 comments on commit 8da1203

Please sign in to comment.