Prechádzať zdrojové kódy

added implementation tag and one new example

Matheus Giovani 2 rokov pred
rodič
commit
e5f79a8374

+ 1 - 0
README.md

@@ -29,6 +29,7 @@ const Renderer = pupper.Renderer;
 
 
 // Compiles the template to a string
 // Compiles the template to a string
 const template = pupper.compileToStringSync(/*pupper*/`
 const template = pupper.compileToStringSync(/*pupper*/`
+template
     .test
     .test
         span.hello-world(id={{ id }})
         span.hello-world(id={{ id }})
             ={- content -}
             ={- content -}

+ 80 - 0
examples/puper vs Vue vs HTML.md

@@ -0,0 +1,80 @@
+# pupper.js vs Vue vs HTML
+
+## To-do list
+
+- pupper
+    ```pug
+    template
+        ul
+            each index, todo in list
+                li(@click="removeItem(index)")
+                    ={{todo}}
+
+    data
+        list = [
+            "Make pancakes."
+        ]
+
+    implementation
+        #removeItem(index)
+            this.list.splice(index, 1);
+    ```
+
+- Vue
+    ```html
+    <template>
+        <ul>
+            <li v-for="index in list" @click="removeItem(index)">
+                {{list[index]}}
+            </li>
+        </ul>
+    </template>
+
+    <script>
+    import { defineComponent } from "vue";
+
+    export default defineComponent({
+        data() {
+            return {
+                list: [
+                    "Make pancakes."
+                ]
+            }
+        },
+        methods: {
+            removeItem(index) {
+                this.list.splice(index, 1);
+            }
+        }
+    })
+    </script>
+    ```
+
+- HTML
+    ```html
+    <ul>
+        <li v-for="index in list" @click="removeItem(index)">
+            {{list[index]}}
+        </li>
+    </ul>
+
+    <script>
+        let list = [
+            "Make pancakes."
+        ];
+
+        const ul = document.querySelector("ul");
+
+        for(let todo of list) {
+            const li = document.createElement("li");
+            li.innerText = todo;
+
+            ul.appendChild(li);
+
+            li.addEventListener("click", (e) => {
+                e.preventDefault();
+                li.remove();
+            });
+        }
+    </script>
+    ```

+ 4 - 2
packages/compiler/package.json

@@ -11,9 +11,12 @@
     "watch:ts": "tsc -watch"
     "watch:ts": "tsc -watch"
   },
   },
   "dependencies": {
   "dependencies": {
-    "alpinejs": "^3.10.2",
+    "html-to-hyperscript": "^0.8.0",
     "pug": "^3.0.2",
     "pug": "^3.0.2",
+    "pug-code-gen": "^2.0.3",
     "pug-error": "^2.0.0",
     "pug-error": "^2.0.0",
+    "pug-linker": "^4.0.0",
+    "pug-parser": "^6.0.0",
     "ts-morph": "^15.1.0"
     "ts-morph": "^15.1.0"
   },
   },
   "types": "./types/",
   "types": "./types/",
@@ -21,7 +24,6 @@
     "@types/node": "^16.7.6",
     "@types/node": "^16.7.6",
     "@types/pug": "^2.0.5",
     "@types/pug": "^2.0.5",
     "debug": "^4.3.2",
     "debug": "^4.3.2",
-    "js-beautify": "^1.14.0",
     "tsc": "^2.0.3",
     "tsc": "^2.0.3",
     "typescript": "^4.4.2",
     "typescript": "^4.4.2",
     "webpack": "^5.51.1",
     "webpack": "^5.51.1",

+ 7 - 4
packages/compiler/src/core/Compiler.ts

@@ -1,4 +1,3 @@
-import { Pug } from "../typings/pug";
 import pug from "pug";
 import pug from "pug";
 import Plugin, { PugAST } from "./Plugin";
 import Plugin, { PugAST } from "./Plugin";
 
 
@@ -126,7 +125,7 @@ export class PupperCompiler {
         return carrier as PugAST;
         return carrier as PugAST;
     }
     }
 
 
-    protected generateJavaScript(ast: Pug.PugAST): string {
+    protected generateJavaScript(ast: pug.PugAST): string {
         ast = this.plugin.preCodeGen(ast);
         ast = this.plugin.preCodeGen(ast);
 
 
         let code = codeGen(ast, this.makePugOptions());
         let code = codeGen(ast, this.makePugOptions());
@@ -187,14 +186,18 @@ export class PupperCompiler {
      * Make the options for the pug compiler.
      * Make the options for the pug compiler.
      */
      */
     protected makePugOptions() {
     protected makePugOptions() {
-        const pugOptions: pug.Options & { filename: string } = {
+        const pugOptions: pug.Options & {
+            filename: string
+            pretty: boolean
+        } = {
             // We use "$render" as the internal function name.
             // We use "$render" as the internal function name.
             name: "$render",
             name: "$render",
             filename: this.getFileName(),
             filename: this.getFileName(),
             compileDebug: this.options.debug || false,
             compileDebug: this.options.debug || false,
             // Always use self to prevent conflicts with other compilers.
             // Always use self to prevent conflicts with other compilers.
             self: true,
             self: true,
-            plugins: []
+            plugins: [],
+            pretty: this.options.debug
         };
         };
 
 
         pugOptions.plugins.push(this.plugin);
         pugOptions.plugins.push(this.plugin);

+ 8 - 0
packages/compiler/src/core/Plugin.ts

@@ -201,6 +201,14 @@ export default class Plugin implements PugPlugin {
             });
             });
     }
     }
 
 
+    /**
+     * Detects the identation for the current parsed file.
+     * @returns 
+     */
+    public detectIdentation() {
+        return this.compiler.contents.match(/^[\t ]+/m)[0];
+    }
+
     /**
     /**
      * Retrieves the compiler options
      * Retrieves the compiler options
      * @returns 
      * @returns 

+ 72 - 45
packages/compiler/src/core/plugin/hooks/component/ScriptParser.ts

@@ -5,7 +5,8 @@ import {
     SyntaxKind,
     SyntaxKind,
     ObjectLiteralExpression,
     ObjectLiteralExpression,
     CallExpression,
     CallExpression,
-    PropertyAccessExpression
+    PropertyAccessExpression,
+    MethodDeclaration
 } from "ts-morph";
 } from "ts-morph";
 
 
 import Plugin from "../../../Plugin";
 import Plugin from "../../../Plugin";
@@ -50,10 +51,18 @@ export class ScriptParser {
             this.processComponentData();
             this.processComponentData();
         }
         }
 
 
-        if (this.component.methods?.length) {
+        if (this.component.implementation.methods?.length) {
             this.processComponentMethods();
             this.processComponentMethods();
         }
         }
 
 
+        if (this.component.implementation.when?.length) {
+            this.processComponentPupperEvents();
+        }
+
+        if (this.component.implementation.events.length) {
+            this.processComponentCustomEvents();
+        }
+
         return this.sourceFile.getText();
         return this.sourceFile.getText();
     }
     }
 
 
@@ -72,7 +81,6 @@ export class ScriptParser {
                 return;
                 return;
             }
             }
 
 
-
             // Left = identifier, Right = initializer
             // Left = identifier, Right = initializer
             const left = binaryExp.getLeft().getText();
             const left = binaryExp.getLeft().getText();
             const right = binaryExp.getRight().getText();
             const right = binaryExp.getRight().getText();
@@ -89,53 +97,51 @@ export class ScriptParser {
      * Processes the component methods.
      * Processes the component methods.
      */
      */
     private processComponentMethods() {
     private processComponentMethods() {
-        const identation = this.component.methods.match(/^(\t| )+/gm)[0];
-
-        let currentMethod = {
-            name: "",
-            body: ""
-        };
-
-        const parsedMethods: {
-            name: string;
-            body: string;
-        }[] = [];
-
-        this.component.methods.split(/[\r\n]/).forEach((line) => {
-            // If the line isn't idented
-            if (!line.startsWith(identation)) {
-                currentMethod = {
-                    name: line.trim(),
-                    body: ""
-                };
-
-                parsedMethods.push(currentMethod);
-            } else {
-                currentMethod.body += line + "\n";
-            }
+        const methodsContainer = this.findOrCreateComponentObj("methods");
+
+        // Retrieve all function declaration expressions
+        this.component.implementation.methods.forEach((method) => {
+            // Add it to the component
+            const fn = methodsContainer.addMethod({
+                name: method.name,
+                parameters: method.parameters
+            });
+
+            fn.setBodyText(method.body);
         });
         });
+    }
 
 
-        // Create the data parser
-        const data = this.project.createSourceFile(
-            "methods.js",
-            parsedMethods.map((method) =>
-                `function ${method.name} {\n${method.body.trimEnd()}\n}`
-            ).join("\n\n")
-        );
+    /**
+     * Processes the component pupper events.
+     */
+    private processComponentPupperEvents() {
+        // Retrieve all function declaration expressions
+        this.component.implementation.when.forEach((when) => {
+            const method = this.findOrCreateComponentMethod(when.name);
+            let currentText = method.getBodyText();
+
+            if (currentText && !currentText.endsWith("\n") && !currentText.endsWith("\r")) {
+                currentText += "\n";
+            }
 
 
-        const componentData = this.findOrCreateComponentObj("methods");
+            method.setBodyText(currentText + when.body);
+        });
+    }
+
+    /**
+     * Processes the component pupper events.
+     */
+    private processComponentCustomEvents() {
+        const methodsContainer = this.findOrCreateComponentObj("methods");
 
 
         // Retrieve all function declaration expressions
         // Retrieve all function declaration expressions
-        data.getChildrenOfKind(SyntaxKind.FunctionDeclaration).forEach((declaration) => {
-            // Add it to the component
-            const method = componentData.addMethod({
-                name: declaration.getName(),
-                parameters: declaration.getParameters().map((param) => ({
-                    name: param.getName()
-                }))
+        this.component.implementation.events.forEach((event) => {
+            const fn = methodsContainer.addMethod({
+                name: event.name,
+                parameters: event.parameters
             });
             });
 
 
-            method.setBodyText(declaration.getBodyText());
+            fn.setBodyText(event.body);
         });
         });
     }
     }
 
 
@@ -150,7 +156,7 @@ export class ScriptParser {
         }
         }
 
 
         // Find the imported components object inside the default export
         // Find the imported components object inside the default export
-        const componentPropsComponents = this.findOrCreateComponentObj("components");
+        const componentsContainer = this.findOrCreateComponentObj("components");
 
 
         // Iterate over all imported components
         // Iterate over all imported components
         for(let alias in this.plugin.sharedData.imports) {
         for(let alias in this.plugin.sharedData.imports) {
@@ -161,7 +167,7 @@ export class ScriptParser {
             });
             });
 
 
             // Add it to the component components
             // Add it to the component components
-            componentPropsComponents.addPropertyAssignment({
+            componentsContainer.addPropertyAssignment({
                 name: alias,
                 name: alias,
                 initializer: alias
                 initializer: alias
             });
             });
@@ -189,6 +195,27 @@ export class ScriptParser {
         }).getInitializer() as ObjectLiteralExpression;
         }).getInitializer() as ObjectLiteralExpression;
     }
     }
 
 
+    /**
+     * Finds or creates a method inside the component object with a given name.
+     * @param name The key to be find or created.
+     * @returns 
+     */
+    private findOrCreateComponentMethod(name: string) {
+        const componentProps = this.findComponentPropsObj();
+
+        // Try finding an existing property with the given key
+        let exportedMethod = componentProps.getProperties()
+            .find((prop) => prop.isKind(SyntaxKind.MethodDeclaration) && prop.getName() === name);
+
+        if (exportedMethod) {
+            return exportedMethod as MethodDeclaration;
+        }
+
+        return componentProps.addMethod({
+            name: name
+        });
+    }
+
     /**
     /**
      * Processes the exported component.
      * Processes the exported component.
      */
      */

+ 53 - 0
packages/compiler/src/core/plugin/nodes/TagNode.ts

@@ -57,6 +57,59 @@ export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
         return attr;
         return attr;
     }
     }
 
 
+    /**
+     * Retrieves the raw node attributes.
+     * @returns 
+     */
+    public getRawAttributes() {
+        return this.pugNode.attrs;
+    }
+
+    /**
+     * Retrieves the parsed node attributes.
+     * @returns 
+     */
+    public getAttributes() {
+        return this.pugNode.attrs.map((attr) => {
+            return {
+                name: attr.name,
+                val: String(attr.val).replace(/^((['"`])(?<escaped>.*?)\2$)|(?<nonescaped>.+?$)/, (match, ignored1, ignored2, p3, p4) => p4 || p3)
+            };
+        })
+    }
+
+    /**
+     * Retrieves all raw CSS classes related to this node.
+     * @returns 
+     */
+    public getRawClasses() {
+        return String(this.pugNode.attrs.find((attr) => attr.name === "class")?.val).trim();
+    }
+
+    /**
+     * Retrieves all CSS classes related to this node.
+     * @returns 
+     */
+    public getClasses() {
+        return this.getRawClasses().replace(/['"]/g, "").split(" ");
+    }
+
+    /**
+     * Retrieves the raw CSS ID related to this node.
+     * @returns 
+     */
+    public getRawId() {
+        return String(this.pugNode.attrs.find((attr) => attr.name === "id")?.val).trim();
+    }
+
+    /**
+     * Retrieves the escaped CSS ID related to this node.
+     * @returns 
+     */
+    public getId() {
+        return this.getRawId().replace(/["']/g, "");
+    }
+
     public toPugNode() {
     public toPugNode() {
         // This can't be undefined
         // This can't be undefined
         this.pugNode.attributeBlocks = this.pugNode.attributeBlocks || [];
         this.pugNode.attributeBlocks = this.pugNode.attributeBlocks || [];

+ 171 - 15
packages/compiler/src/core/plugin/phases/PrepareComponentsHook.ts

@@ -1,19 +1,34 @@
-import { IPluginNode } from "../../Plugin";
+import { IPluginNode, PugToken } from "../../Plugin";
 import { Hook } from "../Hook";
 import { Hook } from "../Hook";
 import { TagNode } from "../nodes/TagNode";
 import { TagNode } from "../nodes/TagNode";
 import { ScriptParser } from "../hooks/component/ScriptParser";
 import { ScriptParser } from "../hooks/component/ScriptParser";
 import { AstNode } from "../nodes/AstNode";
 import { AstNode } from "../nodes/AstNode";
-import { Console } from "console";
 import { PupperCompiler } from "../../Compiler";
 import { PupperCompiler } from "../../Compiler";
 import { CompilerNode } from "../../../model/core/nodes/CompilerNode";
 import { CompilerNode } from "../../../model/core/nodes/CompilerNode";
 
 
 const DefaultExportSymbol = Symbol("ExportedComponent");
 const DefaultExportSymbol = Symbol("ExportedComponent");
 
 
+interface IImplementation {
+    name: string;
+    parameters: {
+        name: string;
+        initializer?: string;
+    }[]
+    body: string;
+}
+
 export interface IComponent {
 export interface IComponent {
     name: string | symbol;
     name: string | symbol;
 
 
+    implementation: {
+        methods?: IImplementation[];
+        when?: IImplementation[];
+        events?: (IImplementation & {
+            covers: string[]   
+        })[];
+    },
+
     template: string;
     template: string;
-    methods?: string;
     script?: string;
     script?: string;
     style?: string;
     style?: string;
     data?: string;
     data?: string;
@@ -31,14 +46,86 @@ export class PrepareComponents extends Hook {
     protected exportedData: Record<string, string> = {};
     protected exportedData: Record<string, string> = {};
 
 
     public beforeStart(code: string) {
     public beforeStart(code: string) {
-        const matches = code.matchAll(/^\s*(methods|data).*[^.]$/gm);
+        const lines = code.replace(/\r\n/g, "\n").split(/\n/g);
+        const identation = this.plugin.detectIdentation();
+        const startWithRegExp = new RegExp("^" + identation + "(?!" + identation + ")");
+        const paramsRegExp = /^.+?\((?<params>.*?)\)$/gm;
+        const singleParamRegExp = /([^?=,]+)(=([^,]*))?/g;
+
+        for(let index = 0; index < lines.length; index++) {
+            let line = lines[index];
+
+            if (line.startsWith("data") && !line.trimEnd().endsWith(".")) {
+                lines[index] = line.trimEnd() + ".";
+                continue;
+            }
+
+            if (!line.startsWith("implementation")) {
+                continue;
+            }
 
 
-        // Add dots to ending "methods" and "data" tags
-        for(let match of matches) {
-            code = code.replace(match[0], match[0].trimEnd() + ".");
+            index++;
+
+            // Retrieve all lines until a non-identation was found
+            do {
+                line = lines[index];
+
+                if (line === undefined) {
+                    break;
+                }
+
+                // Ignore empty lines
+                if (line.length === 0) {
+                    index++;
+                    continue;
+                }
+
+                // If the line starts with one identation level
+                // but doesn't end with a dot and isn't a comment
+                if (line.match(startWithRegExp) && !line.trim().endsWith(".") && !line.trimStart().startsWith("//")) {
+                    // Append a dot at the end of it
+                    lines[index] = line.trimEnd() + ".";
+
+                    let identifier = line.trimStart();
+
+                    // If it's a "when"
+                    if (identifier.startsWith("when")) {
+                        // Replace it with the internal "p-when"
+                        identifier = identifier.replace("when", "event-when");
+                    } else
+                    // If it's not an event
+                    if (!identifier.startsWith("event")) {
+                        // Assume it's a method then
+                        identifier = identifier.replace(identifier, "method" + identifier);
+                    }
+
+                    // Try matching params against the identifier
+                    const matchedParams = paramsRegExp.exec(identifier);
+
+                    // If matched
+                    if (matchedParams) {                        
+                        // Extract all single params
+                        const singleParams = matchedParams.groups.params.matchAll(singleParamRegExp);
+
+                        // Iterate over all params
+                        for(let param of singleParams) {
+                            // If it doesn't have a initializer
+                            if (param[2] === undefined) {
+                                // Strictly add an initializer to it
+                                identifier = identifier.replace(param[0], param[0] + " = undefined");
+                            }
+                        }
+                    }
+
+                    // Replace the identifier with the new one
+                    lines[index] = lines[index].replace(line.trimStart(), identifier);
+                }
+
+                index++;
+            } while(line.length === 0 || line.startsWith(identation));
         }
         }
 
 
-        return code;
+        return lines.join("\n");
     }
     }
 
 
     public parse(nodes: IPluginNode[]) {
     public parse(nodes: IPluginNode[]) {
@@ -88,8 +175,8 @@ export class PrepareComponents extends Hook {
         const isRootComponent = node instanceof AstNode;
         const isRootComponent = node instanceof AstNode;
         const name = !isRootComponent ? node.getAttribute("name")?.replace(/"/g, "") : DefaultExportSymbol;
         const name = !isRootComponent ? node.getAttribute("name")?.replace(/"/g, "") : DefaultExportSymbol;
 
 
+        const implementation = node.findFirstChildByTagName("implementation");
         const template = node.findFirstChildByTagName("template");
         const template = node.findFirstChildByTagName("template");
-        const methods = node.findFirstChildByTagName("methods");
         const script = node.findFirstChildByTagName("script");
         const script = node.findFirstChildByTagName("script");
         const style = node.findFirstChildByTagName("style");
         const style = node.findFirstChildByTagName("style");
         const data = node.findFirstChildByTagName("data");
         const data = node.findFirstChildByTagName("data");
@@ -107,8 +194,12 @@ export class PrepareComponents extends Hook {
          */
          */
         const component: IComponent = {
         const component: IComponent = {
             name,
             name,
+            implementation: {
+                methods: [],
+                when: [],
+                events: []
+            },
             template: null,
             template: null,
-            methods: null,
             script: null,
             script: null,
             style: null,
             style: null,
             data: null,
             data: null,
@@ -143,11 +234,11 @@ export class PrepareComponents extends Hook {
             );
             );
 
 
             // Detect identation
             // Detect identation
-            const identation = /^([\t\n]*) */.exec(lines[0]);
+            const identation = this.plugin.detectIdentation();
 
 
             const contents = lines
             const contents = lines
                 // Replace the first identation
                 // Replace the first identation
-                .map((line) => line.replace(identation[0], ""))
+                .map((line) => line.replace(identation, ""))
                 .join("\n");
                 .join("\n");
 
 
             const templateAsString = new PupperCompiler(this.compiler.options).compileTemplate(contents);
             const templateAsString = new PupperCompiler(this.compiler.options).compileTemplate(contents);
@@ -170,8 +261,73 @@ export class PrepareComponents extends Hook {
         }
         }
 
 
         // If has methods
         // If has methods
-        if (methods) {
-            component.methods = this.consumeChildrenAsString(methods);
+        if (implementation) {
+            // Iterate over all children
+            implementation.getChildren().forEach((child) => {
+                // Ignore comments
+                if (child.isType("Comment")) {
+                    return;
+                }
+
+                // If it's not a tag
+                if (!(child instanceof TagNode)) {
+                    throw this.plugin.compiler.makeParseError("The implementation tag should only contain methods and events, found a " + child.getType() + ".", {
+                        line: child.getLine(),
+                        column: child.getColumn()
+                    });
+                }
+
+                // If it isn't an event or method
+                if (!["method", "event", "event-when"].includes(child.getName())) {
+                    throw this.plugin.compiler.makeParseError("The implementation tag should only contain methods, found an invalid tag " + child.getName() + ".", {
+                        line: child.getLine(),
+                        column: child.getColumn()
+                    });
+                }
+
+                switch(child.getName()) {
+                    // If it's a "method"
+                    case "method":
+                        // Add it to the methods
+                        component.implementation.methods.push({
+                            name: child.getId(),
+                            parameters: child.getRawAttributes().filter((attr) => attr.name !== "class" && attr.name !== "id").map((attr) => ({
+                                name: attr.name,
+                                initializer: attr.val === "undefined" ? undefined : String(attr.val)
+                            })),
+                            body: this.consumeChildrenAsString(child)
+                        });
+                    break;
+
+                    // If it's a "when"
+                    case "event-when":
+                        // Add it to the when implementations
+                        component.implementation.when.push({
+                            name: child.getId(),
+                            parameters: child.getAttributes().map((attr) => ({
+                                name: attr.name,
+                                initializer: attr.val
+                            })),
+                            body: this.consumeChildrenAsString(child)
+                        });
+                    break;
+
+                    // If it's an "event"
+                    case "event":
+                        // Add it to the events implementations
+                        component.implementation.events.push({
+                            // Events has the prefix "$$p_" to prevent conflicts.
+                            name: "$$p_" + child.getId(),
+                            parameters: child.getAttributes().filter((attr) => attr.name !== "class" && attr.name !== "id").map((attr) => ({
+                                name: attr.name,
+                                initializer: attr.val
+                            })),
+                            body: this.consumeChildrenAsString(child),
+                            covers: child.getClasses()
+                        });
+                    break;
+                }
+            });
         }
         }
 
 
         return component;
         return component;
@@ -179,6 +335,6 @@ export class PrepareComponents extends Hook {
 
 
     protected consumeChildrenAsString(node: CompilerNode) {
     protected consumeChildrenAsString(node: CompilerNode) {
         this.plugin.parseChildren(node);
         this.plugin.parseChildren(node);
-        return node.getChildren().map((child) => child.getProp("val")).join("");
+        return node.getChildren().map((child) => child.getProp("val")).join("").trimEnd();
     }
     }
 };
 };

+ 6 - 0
packages/compiler/src/index.ts

@@ -5,6 +5,12 @@ export = class Pupper {
         return new Pupper();
         return new Pupper();
     }
     }
 
 
+    /**
+     * Compiles a component to a string.
+     * @param template The component to be rendered.
+     * @param options The compilation options.
+     * @returns 
+     */
     public compileToString(template: string, options: ICompilerOptions) {
     public compileToString(template: string, options: ICompilerOptions) {
         return new PupperCompiler(options).compileComponent(template);
         return new PupperCompiler(options).compileComponent(template);
     }  
     }  

+ 8 - 0
packages/compiler/src/model/core/nodes/CompilerNode.ts

@@ -170,6 +170,14 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
         return prop in this.pugNode;
         return prop in this.pugNode;
     }
     }
 
 
+    /**
+     * Retrieves the node type.
+     * @returns 
+     */
+    public getType() {
+        return this.pugNode.type;
+    }
+
     /**
     /**
      * Checks if the node has the given type.
      * Checks if the node has the given type.
      * @param type The type to be checked.
      * @param type The type to be checked.

+ 5 - 5
packages/compiler/src/typings/pug-code-gen.d.ts

@@ -1,7 +1,7 @@
-import pug from "./pug";
-
-export declare module "pug-code-gen" {
-    export declare interface ICodeGenOptions {
+declare module "pug-code-gen" {
+    import type pug from "./pug";
+    
+    declare interface ICodeGenOptions {
         compileDebug?: boolean;
         compileDebug?: boolean;
         pretty?: boolean;
         pretty?: boolean;
         inlineRuntimeFunctions?: boolean;
         inlineRuntimeFunctions?: boolean;
@@ -11,5 +11,5 @@ export declare module "pug-code-gen" {
         doctype?: string
         doctype?: string
     }
     }
 
 
-    declare export default function CodeGen(ast: pug.Pug.PugAST, options: ICodeGenOptions): string;
+    export default function CodeGen(ast: pug.Pug.PugAST, options: ICodeGenOptions): string;
 }
 }

+ 9 - 2
packages/compiler/src/typings/pug-error.d.ts

@@ -1,8 +1,15 @@
 declare module "pug-error" {
 declare module "pug-error" {
-    export default (code: string | number, message: string, props: {
+    declare interface IPugError extends Error {
+        src: string;
+        line: number;
+        column: number;
+        filename: string;
+    }
+
+    export default function PugError(code: string | number, message: string, props: {
         line: number;
         line: number;
         column: number;
         column: number;
         filename: string;
         filename: string;
         src: string;
         src: string;
-    }) => Error;
+    }): IPugError;
 }
 }

+ 5 - 0
packages/compiler/src/typings/pug-linker.d.ts

@@ -0,0 +1,5 @@
+declare module "pug-linker" {
+    import type pug from "pug";
+    
+    export default function Link(ast: pug.PugAST): pug.PugAST;
+}

+ 4 - 4
packages/compiler/src/typings/pug-parser.d.ts

@@ -1,11 +1,11 @@
-import pug from "./pug";
+declare module "pug-parser" {
+    import type pug from "pug";
 
 
-export declare module "pug-parser" {
     declare interface IParserOptions {
     declare interface IParserOptions {
         filename?: string;
         filename?: string;
-        plugins?: pug.Pug.PugPlugin[];
+        plugins?: pug.PugPlugin[];
         src?: string;
         src?: string;
     }
     }
 
 
-    declare export default function Parse(ast: pug.Pug.PugToken[], options: IParserOptions): pug.Pug.PugAST;
+    export default function Parse(ast: pug.PugToken[], options: IParserOptions): pug.PugAST;
 }
 }

+ 4 - 6
packages/compiler/src/typings/pug.d.ts

@@ -1,8 +1,6 @@
 import type pug from "pug";
 import type pug from "pug";
 import type PugLexer from "pug-lexer";
 import type PugLexer from "pug-lexer";
 
 
-import { LexTokenType } from "pug-lexer";
-
 export declare namespace Pug {
 export declare namespace Pug {
     export interface LexerPlugin extends Record<string, CallableFunction> {
     export interface LexerPlugin extends Record<string, CallableFunction> {
         /**
         /**
@@ -18,7 +16,7 @@ export declare namespace Pug {
      * Represents a pug token
      * Represents a pug token
      */
      */
     export interface PugToken {
     export interface PugToken {
-        type: LexTokenType,
+        type: PugLexer.LexTokenType,
         loc?: Record<string, any>,
         loc?: Record<string, any>,
         val?: string,
         val?: string,
         name?: string,
         name?: string,
@@ -47,9 +45,9 @@ export declare namespace Pug {
      * Represents a single pug node attribute.
      * Represents a single pug node attribute.
      */
      */
     export interface PugNodeAttribute {
     export interface PugNodeAttribute {
-        name: string,
-        val: string,
-        mustEscape: boolean
+        name: string;
+        val: string | boolean | number;
+        mustEscape: boolean;
     }
     }
 
 
     /**
     /**

+ 4 - 4
packages/compiler/tsconfig.json

@@ -5,11 +5,11 @@
         "outDir": "./out",
         "outDir": "./out",
         "declarationDir": "./types",
         "declarationDir": "./types",
         "typeRoots": [
         "typeRoots": [
-            "./src/typings",
-            "./node_modules/@types",
-            "../../node_modules/@types"
+            "./src/typings/",
+            "./node_modules/@types/",
+            "../../node_modules/@types/"
         ]
         ]
     },
     },
-    "include": ["./src/**/*.ts"],
+    "include": ["./src/**/*.ts", "./src/**/*.d.ts"],
     "exclude": ["node_modules"],
     "exclude": ["node_modules"],
 }
 }

+ 14 - 3
test/templates/template.pupper

@@ -75,6 +75,17 @@ data
         }
         }
     ]
     ]
 
 
-methods
-    onClickPuppy(puppy)
-        alert("You clicked puppy " + puppy.id + "! :D");
+implementation
+    //- Declaring methods
+    #onClickPuppy(puppy)
+        alert("You clicked puppy " + puppy.id + "! :D");
+
+    //- Listening to pupper.js events.
+    when#created
+        console.log("The component was created.");
+
+    when#mounted
+        console.log("The component was mounted.");
+
+    //- Listening to multiple events with one ID'ed listener.
+    event#listener.keyUp.keyPress