@katabatic/compiler 1.0.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.
Files changed (78) hide show
  1. package/README.md +3 -0
  2. package/package.json +32 -0
  3. package/src/analyse/context.js +31 -0
  4. package/src/analyse/index.js +47 -0
  5. package/src/analyse/visitors/AssignmentExpression.js +14 -0
  6. package/src/analyse/visitors/Attribute.js +11 -0
  7. package/src/analyse/visitors/CallExpression.js +36 -0
  8. package/src/analyse/visitors/ClassBody.js +20 -0
  9. package/src/analyse/visitors/ClassDeclaration.js +5 -0
  10. package/src/analyse/visitors/CustomElement.js +10 -0
  11. package/src/analyse/visitors/EachBlock.js +10 -0
  12. package/src/analyse/visitors/Element.js +16 -0
  13. package/src/analyse/visitors/ExpressionTag.js +9 -0
  14. package/src/analyse/visitors/IfBlock.js +16 -0
  15. package/src/analyse/visitors/ImportDeclaration.js +35 -0
  16. package/src/analyse/visitors/MethodDefinition.js +17 -0
  17. package/src/analyse/visitors/Program.js +24 -0
  18. package/src/analyse/visitors/PropertyDefinition.js +12 -0
  19. package/src/analyse/visitors/Selector.js +23 -0
  20. package/src/analyse/visitors/Template.js +14 -0
  21. package/src/builders.js +1663 -0
  22. package/src/checkers.js +120 -0
  23. package/src/css-matcher.js +100 -0
  24. package/src/css-transform.js +21 -0
  25. package/src/css.js +65 -0
  26. package/src/exp-matcher.js +65 -0
  27. package/src/id-matcher.js +17 -0
  28. package/src/index.js +19 -0
  29. package/src/module-matcher.js +17 -0
  30. package/src/parser/attributes.js +66 -0
  31. package/src/parser/each-block.js +73 -0
  32. package/src/parser/element.js +115 -0
  33. package/src/parser/expression.js +44 -0
  34. package/src/parser/if-block.js +71 -0
  35. package/src/parser/index.js +10 -0
  36. package/src/parser/parser.js +259 -0
  37. package/src/parser/root.js +33 -0
  38. package/src/parser/script.js +59 -0
  39. package/src/parser/style.js +57 -0
  40. package/src/parser/template.js +30 -0
  41. package/src/parser/tokentype.js +75 -0
  42. package/src/router/html.js +18 -0
  43. package/src/router/index.js +130 -0
  44. package/src/transform/context.js +74 -0
  45. package/src/transform/index.js +52 -0
  46. package/src/transform/static/index.js +40 -0
  47. package/src/transform/static/visitors/Attribute.js +27 -0
  48. package/src/transform/static/visitors/EachBlock.js +23 -0
  49. package/src/transform/static/visitors/Element.js +17 -0
  50. package/src/transform/static/visitors/ExpressionTag.js +6 -0
  51. package/src/transform/static/visitors/IfBlock.js +28 -0
  52. package/src/transform/static/visitors/Program.js +10 -0
  53. package/src/transform/static/visitors/Script.js +9 -0
  54. package/src/transform/static/visitors/SlotElement.js +8 -0
  55. package/src/transform/static/visitors/Style.js +9 -0
  56. package/src/transform/static/visitors/Template.js +12 -0
  57. package/src/transform/static/visitors/Text.js +5 -0
  58. package/src/transform/visitors/AssignmentExpression.js +7 -0
  59. package/src/transform/visitors/Attribute.js +79 -0
  60. package/src/transform/visitors/CallExpression.js +17 -0
  61. package/src/transform/visitors/ClassBody.js +36 -0
  62. package/src/transform/visitors/CustomElement.js +43 -0
  63. package/src/transform/visitors/EachBlock.js +39 -0
  64. package/src/transform/visitors/Element.js +49 -0
  65. package/src/transform/visitors/ExpressionTag.js +6 -0
  66. package/src/transform/visitors/Fragment.js +42 -0
  67. package/src/transform/visitors/Identifier.js +10 -0
  68. package/src/transform/visitors/IfBlock.js +55 -0
  69. package/src/transform/visitors/ImportDeclaration.js +21 -0
  70. package/src/transform/visitors/MethodDefinition.js +115 -0
  71. package/src/transform/visitors/Program.js +65 -0
  72. package/src/transform/visitors/PropertyDefinition.js +7 -0
  73. package/src/transform/visitors/Selector.js +41 -0
  74. package/src/transform/visitors/Style.js +11 -0
  75. package/src/transform/visitors/Template.js +44 -0
  76. package/src/transform/visitors/Text.js +5 -0
  77. package/src/utils/misc.js +9 -0
  78. package/src/utils/template.js +15 -0
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Katabatic compiler
2
+
3
+ [Katabatic](https://github.com/katabatic-js/katabatic)'s compiler
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@katabatic/compiler",
3
+ "license": "MIT",
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/katabatic-js/katabatic.git",
9
+ "directory": "packages/compiler"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./src/index.js"
14
+ },
15
+ "./router": {
16
+ "import": "./src/router/index.js"
17
+ }
18
+ },
19
+ "devDependencies": {
20
+ "@types/css-tree": "^2.3.10",
21
+ "@types/node": "^22.15.21",
22
+ "typescript": "~5.7.2",
23
+ "vitest": "^3.2.4"
24
+ },
25
+ "dependencies": {
26
+ "@web/dev-server": "^0.4.6",
27
+ "acorn": "^8.14.1",
28
+ "css-tree": "^3.1.0",
29
+ "esrap": "^1.4.6",
30
+ "zimmerframe": "^1.1.2"
31
+ }
32
+ }
@@ -0,0 +1,31 @@
1
+ export function getStyleSheet(ctx) {
2
+ const root = ctx.path[0]
3
+ return root.template.fragment.nodes.find((n) => n.type === 'Style')?.content
4
+ }
5
+
6
+ export function getTemplate(ctx) {
7
+ const root = ctx.path[0]
8
+ return root.template
9
+ }
10
+
11
+ export function getProgram(ctx) {
12
+ const root = ctx.path[0]
13
+ return root.script.content
14
+ }
15
+
16
+ export function getScript(ctx) {
17
+ return ctx.path.toReversed().find((n) => n.type === 'Script')
18
+ }
19
+
20
+ export function getBlocks(ctx) {
21
+ return ctx.path.toReversed().filter((n) => n.type === 'EachBlock')
22
+ }
23
+
24
+ export function isInConstructor(ctx) {
25
+ for (const node of ctx.path.toReversed()) {
26
+ if (node.type === 'ArrowFunctionExpression') return false
27
+ if (node.type === 'FunctionDeclaration') return false
28
+ if (node.type === 'MethodDefinition') return node.kind === 'constructor'
29
+ }
30
+ return false
31
+ }
@@ -0,0 +1,47 @@
1
+ import { walk } from 'zimmerframe'
2
+ import { CallExpression } from './visitors/CallExpression.js'
3
+ import { ClassDeclaration } from './visitors/ClassDeclaration.js'
4
+ import { Template } from './visitors/Template.js'
5
+ import { Element } from './visitors/Element.js'
6
+ import { CssTree, Selector } from './visitors/Selector.js'
7
+ import { Attribute } from './visitors/Attribute.js'
8
+ import { IfBlock } from './visitors/IfBlock.js'
9
+ import { ClassBody } from './visitors/ClassBody.js'
10
+ import { Program } from './visitors/Program.js'
11
+ import { ExpressionTag } from './visitors/ExpressionTag.js'
12
+ import { EachBlock } from './visitors/EachBlock.js'
13
+ import { AssignmentExpression } from './visitors/AssignmentExpression.js'
14
+ import { MethodDefinition } from './visitors/MethodDefinition.js'
15
+ import { PropertyDefinition } from './visitors/PropertyDefinition.js'
16
+ import { ImportDeclaration } from './visitors/ImportDeclaration.js'
17
+ import { CustomElement } from './visitors/CustomElement.js'
18
+
19
+ const visitors = {
20
+ Program,
21
+ ImportDeclaration,
22
+ CallExpression,
23
+ ClassDeclaration,
24
+ ClassBody,
25
+ AssignmentExpression,
26
+ MethodDefinition,
27
+ PropertyDefinition,
28
+ Template,
29
+ Element,
30
+ CustomElement,
31
+ Attribute,
32
+ ExpressionTag,
33
+ IfBlock,
34
+ EachBlock,
35
+ Selector,
36
+ ...CssTree
37
+ }
38
+
39
+ export function analyse(ast) {
40
+ const state = {
41
+ isStatic: false
42
+ }
43
+
44
+ walk(ast, state, visitors)
45
+
46
+ return state
47
+ }
@@ -0,0 +1,14 @@
1
+ import * as is from '../../checkers.js'
2
+ import { isInConstructor } from '../context.js'
3
+
4
+ export function AssignmentExpression(node, ctx) {
5
+ if (is.thisMember(node.left) && isInConstructor(ctx)) {
6
+ const isPrivate = is.privateId(node.left.property)
7
+ const customElement = isPrivate ? ctx.state.customElement.private : ctx.state.customElement
8
+
9
+ node.metadata ??= {}
10
+ node.metadata.isPrivate = isPrivate
11
+ node.metadata.isProperty = true
12
+ customElement.properties.push(node.left.property.name)
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function Attribute(node, ctx) {
4
+ ctx.next()
5
+
6
+ if (is.classAttribute(node, true) || is.idAttribute(node, true)) {
7
+ node.metadata ??= {}
8
+ node.metadata.isScoped = true
9
+ return
10
+ }
11
+ }
@@ -0,0 +1,36 @@
1
+ import * as is from '../../checkers.js'
2
+ import { matchQuerySelector } from '../../css-matcher.js'
3
+ import { matchElementById } from '../../id-matcher.js'
4
+ import { getTemplate } from '../context.js'
5
+
6
+ export function CallExpression(node, ctx) {
7
+ ctx.next()
8
+
9
+ if (is.getElementById(node)) {
10
+ const id = node.arguments[0].value
11
+ const template = getTemplate(ctx)
12
+ const isScoped = matchElementById(id, template)
13
+
14
+ node.metadata ??= {}
15
+ node.metadata.isGetElementById = true
16
+ node.metadata.isScoped = isScoped
17
+ return
18
+ }
19
+
20
+ if (is.querySelector(node)) {
21
+ const selector = node.arguments[0].value
22
+ const template = getTemplate(ctx)
23
+ const [isScoped, selectorList] = matchQuerySelector(selector, template)
24
+
25
+ node.metadata ??= {}
26
+ node.metadata.isQuerySelector= true
27
+ node.metadata.isScoped = isScoped
28
+ node.metadata.selectorList = selectorList
29
+ return
30
+ }
31
+
32
+ if (is.defineCustomElement(node)) {
33
+ const name = node.arguments[0].value
34
+ ctx.state.customElement.name = name
35
+ }
36
+ }
@@ -0,0 +1,20 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function ClassBody(node, ctx) {
4
+ ctx.next()
5
+
6
+ const hasConstructor = node.body.some(is.constructor)
7
+ const hasConnectedCallbackMethod = node.body.some(is.connectedCallback)
8
+ const hasDisconnectedCallbackMethod = node.body.some(is.disconnectedCallback)
9
+ const hasGetAttribute = node.body.some(is.getAttribute)
10
+ const hasAttributeChangedCallback = node.body.some(is.attributeChangedCallback)
11
+ const hasObservedAttributes = node.body.some(is.observedAttributes)
12
+
13
+ node.metadata ??= {}
14
+ node.metadata.hasConstructor = hasConstructor
15
+ node.metadata.hasConnectedCallbackMethod = hasConnectedCallbackMethod
16
+ node.metadata.hasDisconnectedCallbackMethod = hasDisconnectedCallbackMethod
17
+ node.metadata.hasGetAttribute = hasGetAttribute
18
+ node.metadata.hasAttributeChangedCallback = hasAttributeChangedCallback
19
+ node.metadata.hasObservedAttributes = hasObservedAttributes
20
+ }
@@ -0,0 +1,5 @@
1
+ export function ClassDeclaration(node, ctx) {
2
+ ctx.next()
3
+
4
+ ctx.state.customElement.className = node.id.name
5
+ }
@@ -0,0 +1,10 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function CustomElement(node, ctx) {
4
+ ctx.next()
5
+
6
+ const hasClass = node.attributes.some(is.classAttribute)
7
+
8
+ node.metadata ??= {}
9
+ node.metadata.hasClass = hasClass
10
+ }
@@ -0,0 +1,10 @@
1
+ import { matchExpression } from '../../exp-matcher.js'
2
+ import { getBlocks, getProgram } from '../context.js'
3
+
4
+ export function EachBlock(node, ctx) {
5
+ ctx.next()
6
+
7
+ const program = getProgram(ctx)
8
+ const blocks = getBlocks(ctx)
9
+ matchExpression(node.expression, program, blocks)
10
+ }
@@ -0,0 +1,16 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function Element(node, ctx) {
4
+ ctx.next()
5
+
6
+ const hasClass = node.attributes.some(is.classAttribute)
7
+
8
+ const bindAttribute = node.attributes.find(is.bindAttribute)
9
+ const hasBinding = !!bindAttribute
10
+ const bindExpression = bindAttribute?.value[0].expression
11
+
12
+ node.metadata ??= {}
13
+ node.metadata.hasClass = hasClass
14
+ node.metadata.hasBinding = hasBinding
15
+ node.metadata.bindExpression = bindExpression
16
+ }
@@ -0,0 +1,9 @@
1
+ import { matchExpression } from "../../exp-matcher.js";
2
+ import { getBlocks, getProgram } from "../context.js";
3
+
4
+ export function ExpressionTag(node, ctx) {
5
+ const program = getProgram(ctx)
6
+ const blocks = getBlocks(ctx)
7
+
8
+ matchExpression(node.expression, program, blocks)
9
+ }
@@ -0,0 +1,16 @@
1
+ import * as is from '../../checkers.js'
2
+ import { matchExpression } from '../../exp-matcher.js'
3
+ import { getBlocks, getProgram } from '../context.js'
4
+
5
+ export function IfBlock(node, ctx) {
6
+ ctx.next()
7
+
8
+ const program = getProgram(ctx)
9
+ const blocks = getBlocks(ctx)
10
+ matchExpression(node.test, program, blocks)
11
+
12
+ const hasElseif = node.alternate?.nodes.some(is.ifBlock) ?? false
13
+
14
+ node.metadata ??= {}
15
+ node.metadata.hasElseif = hasElseif
16
+ }
@@ -0,0 +1,35 @@
1
+ import { matchModule } from '../../module-matcher.js'
2
+ import { getTemplate } from '../context.js'
3
+
4
+ export function ImportDeclaration(node, ctx) {
5
+ const template = getTemplate(ctx)
6
+
7
+ const { dirname, filename, extension } = parse(node.source.value)
8
+ if (filename.includes('-')) {
9
+ const index = ctx.state.modules.length
10
+ const isModule = matchModule(filename, index, template)
11
+
12
+ if (isModule) {
13
+ node.metadata ??= {}
14
+ node.metadata.isModule = isModule
15
+ node.metadata.dirname = dirname
16
+ node.metadata.filename = filename
17
+ node.metadata.extension = extension
18
+ node.metadata.index = index
19
+
20
+ ctx.state.modules.push(filename)
21
+ }
22
+ }
23
+ }
24
+
25
+ function parse(path) {
26
+ const dirnameTokens = path.split('/')
27
+ const filenameTokens = dirnameTokens.pop().split('.')
28
+ const extension = filenameTokens.pop()
29
+
30
+ return {
31
+ dirname: dirnameTokens.join('/'),
32
+ filename: filenameTokens.join('.'),
33
+ extension
34
+ }
35
+ }
@@ -0,0 +1,17 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function MethodDefinition(node, ctx) {
4
+ ctx.next()
5
+
6
+ const isPrivate = is.privateId(node.key)
7
+ const customElement = isPrivate ? ctx.state.customElement.private : ctx.state.customElement
8
+
9
+ node.metadata ??= {}
10
+ node.metadata.isPrivate = isPrivate
11
+
12
+ if (node.kind === 'method') {
13
+ customElement.methods.push(node.key.name)
14
+ } else if (node.kind === 'set') {
15
+ customElement.setters.push(node.key.name)
16
+ }
17
+ }
@@ -0,0 +1,24 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function Program(node, ctx) {
4
+ const modules = []
5
+ const customElement = {
6
+ properties: [],
7
+ methods: ['getAttribute'],
8
+ setters: [],
9
+ private: {
10
+ properties: [],
11
+ methods: [],
12
+ setters: []
13
+ }
14
+ }
15
+
16
+ ctx.next({ ...ctx.state, modules, customElement })
17
+
18
+ const hasDefineCustomElement = node.body.some(is.defineCustomElement)
19
+
20
+ node.metadata ??= {}
21
+ node.metadata.hasDefineCustomElement = hasDefineCustomElement
22
+ node.metadata.modules = modules
23
+ node.metadata.customElement = customElement
24
+ }
@@ -0,0 +1,12 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function PropertyDefinition(node, ctx) {
4
+ if (!node.static) {
5
+ const isPrivate = is.privateId(node.key)
6
+ const customElement = isPrivate ? ctx.state.customElement.private : ctx.state.customElement
7
+
8
+ node.metadata ??= {}
9
+ node.metadata.isPrivate = isPrivate
10
+ customElement.properties.push(node.key.name)
11
+ }
12
+ }
@@ -0,0 +1,23 @@
1
+ import { matchSelector } from '../../css-matcher.js'
2
+ import { getTemplate } from '../context.js'
3
+
4
+ export function Selector(node, ctx) {
5
+ node = CssTreeNodeFix(node, ctx)
6
+
7
+ const template = getTemplate(ctx)
8
+ const isUsed = matchSelector(node, template)
9
+
10
+ node.metadata ??= {}
11
+ node.metadata.isUsed = isUsed
12
+ }
13
+
14
+ export const CssTree = {
15
+ StyleSheet: CssTreeNodeFix,
16
+ SelectorList: CssTreeNodeFix,
17
+ PseudoClassSelector: CssTreeNodeFix
18
+ }
19
+
20
+ function CssTreeNodeFix(node, ctx) {
21
+ const children = node.children?.map((c) => ctx.visit(c)) ?? null
22
+ return { ...node, children }
23
+ }
@@ -0,0 +1,14 @@
1
+ import * as is from '../../checkers.js'
2
+
3
+ export function Template(node, ctx) {
4
+ ctx.next()
5
+
6
+ const isStatic = node.attributes.some(is.staticAttribute)
7
+ const shadowRootMode = node.attributes.find(is.shadowRootModeAttribute)?.value[0]?.data
8
+
9
+ node.metadata ??= {}
10
+ node.metadata.shadowRootMode = shadowRootMode
11
+
12
+ ctx.state.isStatic = isStatic
13
+ }
14
+