1
0
Эх сурвалжийг харах

added listeners and updated vscode ext

Matheus Giovani 2 жил өмнө
parent
commit
db2b2e9838

+ 14 - 0
packages/compiler/src/core/Compiler.ts

@@ -168,6 +168,20 @@ export class PupperCompiler {
         return rendered;///*js*/`function $h(h) { return ${htmlToHs({ syntax: "h" })(rendered)}; }`;
     }
 
+    /**
+     * Sets the shared data to this compiler plugin.
+     * @param data The data to be shared with the plugin.
+     * @returns 
+     */
+    public setSharedData(data: any) {
+        this.plugin.sharedData = data;
+        return this;
+    }
+
+    /**
+     * Retrieves the name of the template being currently compiled.
+     * @returns 
+     */
     public getFileName() {
         return this.options.fileName || this.options.pug.filename || "template.pupper";
     }

+ 5 - 2
packages/compiler/src/core/Plugin.ts

@@ -8,6 +8,7 @@ import { PupperToAlpineHook } from "./plugin/hooks/PupperToAlpineHook";
 import { ImportHook } from "./plugin/hooks/ImportHook";
 import { CompilerNode } from "../model/core/nodes/CompilerNode";
 import { StyleAndScriptHook } from "./plugin/hooks/StyleAndScriptHook";
+import { ListenerHook } from "./plugin/hooks/ListenerHook";
 
 import { AstNode } from "./plugin/nodes/AstNode";
 import { EachNode } from "./plugin/nodes/EachNode";
@@ -19,6 +20,7 @@ import { Pug } from "../typings/pug";
 import { TemplateTagNode } from "./plugin/nodes/tags/TemplateTagNode";
 import { PrepareComponents } from "./plugin/phases/PrepareComponentsHook";
 import { CompilationType, PupperCompiler } from "./Compiler";
+
 import lex from "pug-lexer";
 
 type THookConstructor = { new(plugin: Plugin): Hook };
@@ -67,7 +69,8 @@ export default class Plugin implements PugPlugin {
         PropertyHook,
         PupperToAlpineHook,
         ImportHook,
-        StyleAndScriptHook
+        StyleAndScriptHook,
+        ListenerHook
     ];
 
     /**
@@ -132,7 +135,7 @@ export default class Plugin implements PugPlugin {
     /**
      * Any data to be shared between hooks and phases.
      */
-    public sharedData: Record<any, any> = {};
+    public sharedData: Record<string, any> = {};
 
     public lex: LexerPlugin;
 

+ 2 - 2
packages/compiler/src/core/plugin/hooks/ImportHook.ts

@@ -40,7 +40,7 @@ export class ImportHook extends Hook {
                 let match = fullImport.match(ImportHook.INLINE_IMPORT_REGEX);
 
                 if (!match) {
-                    throw this.makeError("INVALID_IMPORT", "Invalid import expression.", {
+                    throw this.compiler.makeParseError("Invalid import expression.", {
                         line: currentToken.loc.line,
                         column: currentToken.loc.column
                     });
@@ -92,7 +92,7 @@ export class ImportHook extends Hook {
                 if (this.plugin.sharedData.imports?.[node.getProp("name")] !== undefined) {
                     // If has a body
                     if (node.hasChildren()) {
-                        throw this.makeError("IMPORT_TAG_WITH_BODY", "Imported tags can't have a body.", {
+                        throw this.compiler.makeParseError("Imported tags can't have a body.", {
                             line: node.getLine(),
                             column: node.getColumn()
                         });

+ 33 - 0
packages/compiler/src/core/plugin/hooks/ListenerHook.ts

@@ -0,0 +1,33 @@
+import { CompilerNode } from "../../../model/core/nodes/CompilerNode";
+import { Hook } from "../Hook";
+import { TagNode } from "../nodes/TagNode";
+import { DefaultExportSymbol, IComponent } from "../phases/PrepareComponentsHook";
+
+export class ListenerHook extends Hook {
+    public parse(nodes: CompilerNode[]) {
+        nodes.forEach((node) => {
+            if (!(node instanceof TagNode)) {
+                return;
+            }
+        
+            // If has a listener
+            if (node.hasAttribute("p-listener")) {
+                // Remove the attribute from it
+                const listenerName = node.removeAttribute("p-listener") as string;
+
+                // Retrieve all events that this listener covers
+                const eventNames = (
+                    this.plugin.sharedData.components[DefaultExportSymbol] as IComponent
+                ).implementation.listeners
+                    .find((e) => e.name === "$$p_" + listenerName).covers;
+
+                // Set them                        
+                for(let event of eventNames) {
+                    node.setAttribute("x-bind:" + event, "$$p_" + listenerName);
+                }
+            }
+        });
+
+        return nodes;
+    }
+}

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

@@ -59,7 +59,7 @@ export class ScriptParser {
             this.processComponentPupperEvents();
         }
 
-        if (this.component.implementation.events.length) {
+        if (this.component.implementation.listeners.length) {
             this.processComponentCustomEvents();
         }
 
@@ -135,7 +135,7 @@ export class ScriptParser {
         const methodsContainer = this.findOrCreateComponentObj("methods");
 
         // Retrieve all function declaration expressions
-        this.component.implementation.events.forEach((event) => {
+        this.component.implementation.listeners.forEach((event) => {
             const fn = methodsContainer.addMethod({
                 name: event.name,
                 parameters: event.parameters

+ 49 - 8
packages/compiler/src/core/plugin/nodes/TagNode.ts

@@ -24,10 +24,19 @@ export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
      * @param name The attribute name to be retrieved.
      * @returns 
      */
-    public getAttribute(name: string) {
+    public getRawAttribute(name: string) {
         return this.pugNode.attrs.find((attr) => attr.name === name)?.val as any;
     }
 
+    /**
+     * Retrieves a node attribute by name.
+     * @param name The attribute name to be retrieved.
+     * @returns 
+     */
+    public getAttribute(name: string) {
+        return this.getAttributes().find((attr) => attr.name === name)?.val.replace(/['"]/g, "").trim();
+    }
+
     /**
      * Sets a node attribute value.
      * @param name The atribute name.
@@ -57,6 +66,22 @@ export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
         return attr;
     }
 
+    /**
+     * Removes an attribute by it's name.
+     * @param name The name of the attribute to be removed.
+     * @returns 
+     */
+    public removeAttribute(name: string) {
+        const removed = this.getAttribute(name);
+
+        this.pugNode.attrs.splice(
+            this.pugNode.attrs.findIndex((attr) => attr.name === name),
+            1
+        );
+
+        return removed;
+    }
+
     /**
      * Retrieves the raw node attributes.
      * @returns 
@@ -70,12 +95,28 @@ export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
      * @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)
-            };
-        })
+        return Object.values(
+            this.pugNode.attrs.reduce((carrier, attr) => {
+                // If this class hasn't been added to the attributes yet
+                if (!(attr.name in carrier)) {
+                    // Add it
+                    carrier[attr.name] = {
+                        name: attr.name,
+                        val: ""
+                    };
+                }
+
+                carrier[attr.name].val += String(attr.val).replace(/^((['"`])(?<escaped>.*?)\2$)|(?<nonescaped>.+?$)/, (match, ignored1, ignored2, p3, p4) => p4 || p3) + " ";
+
+                return carrier;
+            }, {} as Record<string, {
+                name: string;
+                val: string
+            }>)
+        ).map((attr) => {
+            attr.val = attr.val.trim();
+            return attr;
+        });
     }
 
     /**
@@ -91,7 +132,7 @@ export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
      * @returns 
      */
     public getClasses() {
-        return this.getRawClasses().replace(/['"]/g, "").split(" ");
+        return this.getAttribute("class").replace(/['"]/g, "").split(" ");
     }
 
     /**

+ 121 - 95
packages/compiler/src/core/plugin/phases/PrepareComponentsHook.ts

@@ -1,4 +1,4 @@
-import { IPluginNode, PugToken } from "../../Plugin";
+import { IPluginNode } from "../../Plugin";
 import { Hook } from "../Hook";
 import { TagNode } from "../nodes/TagNode";
 import { ScriptParser } from "../hooks/component/ScriptParser";
@@ -6,7 +6,7 @@ import { AstNode } from "../nodes/AstNode";
 import { PupperCompiler } from "../../Compiler";
 import { CompilerNode } from "../../../model/core/nodes/CompilerNode";
 
-const DefaultExportSymbol = Symbol("ExportedComponent");
+export const DefaultExportSymbol = Symbol("ExportedComponent");
 
 interface IImplementation {
     name: string;
@@ -23,7 +23,7 @@ export interface IComponent {
     implementation: {
         methods?: IImplementation[];
         when?: IImplementation[];
-        events?: (IImplementation & {
+        listeners?: (IImplementation & {
             covers: string[]   
         })[];
     },
@@ -46,6 +46,9 @@ export class PrepareComponents extends Hook {
     protected exportedData: Record<string, string> = {};
 
     public beforeStart(code: string) {
+        this.plugin.sharedData.components = {};
+        this.components = this.plugin.sharedData.components;
+
         const lines = code.replace(/\r\n/g, "\n").split(/\n/g);
         const identation = this.plugin.detectIdentation();
         const startWithRegExp = new RegExp("^" + identation + "(?!" + identation + ")");
@@ -93,8 +96,8 @@ export class PrepareComponents extends Hook {
                         // Replace it with the internal "p-when"
                         identifier = identifier.replace("when", "event-when");
                     } else
-                    // If it's not an event
-                    if (!identifier.startsWith("event")) {
+                    // If it's not an event or a listener
+                    if (!identifier.startsWith("event") && !identifier.startsWith("listener")) {
                         // Assume it's a method then
                         identifier = identifier.replace(identifier, "method" + identifier);
                     }
@@ -135,13 +138,9 @@ export class PrepareComponents extends Hook {
                 continue;
             }
 
-            // Parse them as a component
-            // Parse the component
+            // Parse as a component
             const component = this.parseComponentNode(node.parent);
 
-            // Save the component
-            this.components[component.name] = component;
-
             break;
         }
 
@@ -197,7 +196,7 @@ export class PrepareComponents extends Hook {
             implementation: {
                 methods: [],
                 when: [],
-                events: []
+                listeners: []
             },
             template: null,
             script: null,
@@ -206,8 +205,11 @@ export class PrepareComponents extends Hook {
             exported: isRootComponent
         };
 
+        // Save the component
+        this.components[component.name] = component;
+
         // If the component is not exported and has no name
-        if (!component.exported && (!name || !name.length)) {
+        if (!component.exported && !name) {
             throw this.compiler.makeParseError("Scoped components must have a name.", {
                 line: node.getLine() || 1,
                 column: node.getColumn()
@@ -215,15 +217,34 @@ export class PrepareComponents extends Hook {
         }
 
         // If the component has no name
-        if (!name || !name.length) {
+        if (!name) {
             // Assume it's the default export
             component.name = DefaultExportSymbol;
         }
 
+        // If has a script
+        if (script) {
+            component.script = this.consumeChildrenAsString(script);
+        }
+
+        // If has a style
+        if (style) {
+            console.log(style);
+        }
+
+        // If has data
+        if (data) {
+            component.data = this.consumeChildrenAsString(data);
+        }
+
+        // If has methods
+        if (implementation) {
+            component.implementation = this.consumeAsImplementation(implementation);
+        }
+
         // If has a template
+        // ATTENTION: templates needs to be parsed after everything as already parsed.
         if (template) {
-            //this.plugin.parseChildren(template, true);
-
             let lines = this.plugin.compiler.contents.split("\n");
 
             const nextNodeAfterTemplate = template.getNextNode();
@@ -241,98 +262,103 @@ export class PrepareComponents extends Hook {
                 .map((line) => line.replace(identation, ""))
                 .join("\n");
 
-            const templateAsString = new PupperCompiler(this.compiler.options).compileTemplate(contents);
+            const compiler = new PupperCompiler(this.plugin.options);
+            compiler.setSharedData(this.plugin.sharedData);
+
+            const templateAsString = compiler.compileTemplate(contents);
             component.template = templateAsString;
         }
 
-        // If has a script
-        if (script) {
-            component.script = this.consumeChildrenAsString(script);
-        }
+        return component;
+    }
 
-        // If has a style
-        if (style) {
-            console.log(style);
-        }
+    /**
+     * Consumes all children nodes from a tag node into a component implementation.
+     * @param node The node to be consumed.
+     * @returns 
+     */
+    protected consumeAsImplementation(node: TagNode) {
+        const implementations: IComponent["implementation"] = {
+            methods: [],
+            when: [],
+            listeners: []
+        };
 
-        // If has data
-        if (data) {
-            component.data = this.consumeChildrenAsString(data);
-        }
+        // Iterate over all children
+        node.getChildren().forEach((child) => {
+            // Ignore comments
+            if (child.isType("Comment")) {
+                return;
+            }
 
-        // If has 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'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", "listener"].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()
+                });
+            }
 
-                // 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
+                    implementations.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)
                     });
-                }
-
-                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;
-                }
-            });
-        }
+                break;
+
+                // If it's a "when"
+                case "event-when":
+                    // Add it to the when implementations
+                    implementations.when.push({
+                        name: child.getId(),
+                        parameters: child.getAttributes().map((attr) => ({
+                            name: attr.name,
+                            initializer: attr.val
+                        })),
+                        body: this.consumeChildrenAsString(child)
+                    });
+                break;
+
+                // If it's a "listener"
+                case "listener":
+                    // Add it to the listeners implementations
+                    implementations.listeners.push({
+                        // Listeners 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 implementations;
     }
 
+    /**
+     * Consumes all children nodes values from a node into a string.
+     * @param node The node to be consumed.
+     * @returns 
+     */
     protected consumeChildrenAsString(node: CompilerNode) {
         this.plugin.parseChildren(node);
         return node.getChildren().map((child) => child.getProp("val")).join("").trimEnd();

+ 94 - 61
packages/vscode-extension/pupper-js/syntaxes/pupper.tmLanguage.json

@@ -11,23 +11,23 @@
         {
             "begin": "^(\\s*)//-",
             "end": "^(?!(\\1\\s)|\\s*$)",
-            "name": "comment.unbuffered.block.pug",
+            "name": "comment.unbuffered.block.pupper",
             "comment": "Unbuffered (pug-only) comments."
         },
         {
             "begin": "^(\\s*)//",
             "end": "^(?!(\\1\\s)|\\s*$)",
-            "name": "string.comment.buffered.block.pug",
+            "name": "string.comment.buffered.block.pupper",
             "comment": "Buffered (html) comments.",
             "patterns": [
                 {
                     "captures": {
                         "1": {
-                            "name": "invalid.illegal.comment.comment.block.pug"
+                            "name": "invalid.illegal.comment.comment.block.pupper"
                         }
                     },
                     "match": "^\\s*(//)(?!-)",
-                    "name": "string.comment.buffered.block.pug",
+                    "name": "string.comment.buffered.block.pupper",
                     "comment": "Buffered comments inside buffered comments will generate invalid html."
                 }
             ]
@@ -35,11 +35,11 @@
         {
             "begin": "<!--",
             "end": "--\\s*>",
-            "name": "comment.unbuffered.block.pug",
+            "name": "comment.unbuffered.block.pupper",
             "patterns": [
                 {
                     "match": "--",
-                    "name": "invalid.illegal.comment.comment.block.pug"
+                    "name": "invalid.illegal.comment.comment.block.pupper"
                 }
             ]
         },
@@ -47,7 +47,7 @@
             "begin": "^(\\s*)(script)",
             "beginCaptures": {
                 "2": {
-                    "name": "entity.name.tag.pug"
+                    "name": "entity.name.tag.pupper"
                 }
             },
             "end": "^(?!(\\1\\s)|\\s*$)(:[\\S]+?$)?",
@@ -90,7 +90,7 @@
             "begin": "^(\\s*)(style)",
             "beginCaptures": {
                 "2": {
-                    "name": "entity.name.tag.pug"
+                    "name": "entity.name.tag.pupper"
                 }
             },
             "end": "^(?!(\\1\\s)|\\s*$)",
@@ -119,12 +119,28 @@
                     "include": "source.css"
                 }
             ]
+        },
+        {
+            "begin": "^(\\s*)(data)",
+            "beginCaptures": {
+                "2": {
+                    "name": "entity.name.tag.pupper"
+                }
+            },
+            "end": "^(?!(\\1\\s)|\\s*$)",
+            "name": "meta.tag.other",
+            "comment": "Data tag.",
+            "patterns": [
+                {
+                    "include": "source.js"
+                }
+            ]
         },
 		{
             "begin": "^(\\s*)(import)\\s*\\((\\S+)\\s+(from)\\s+((['\"])(.+?)\\6)\\)",
             "beginCaptures": {
                 "2": {
-                    "name": "entity.name.tag.pug"
+                    "name": "storage.type.function.pupper"
                 },
 				"3": {
 					"name": "keyword.control"
@@ -160,17 +176,34 @@
                 }
             ]
         },
+        {
+            "begin": "^(\\s*)(when|listener|event)(\\#\\S*?)(\\.\\S*)?",
+            "beginCaptures": {
+                "2": {
+                    "name": "storage.type.function.pupper"
+                },
+				"3": {
+					"name": "entity.name.function"
+				},
+                "4": {
+                    "name": "entity.other.attribute-name.pupper"
+                }
+            },
+            "end": "^(?!(\\1\\s)|\\s*$)",
+            "name": "meta.tag.other",
+            "comment": "when, event and listener operators."
+        },
         {
             "begin": "^(\\s*)(?=[\\w.#].*?\\.$)(?=(?:(?:(?:(?:(?:#[\\w-]+)|(?:\\.[\\w-]+))|(?:(?:[#!]\\{[^}]*\\})|(?:\\w(?:(?:[\\w:-]+[\\w-])|(?:[\\w-]*)))))(?:(?:#[\\w-]+)|(?:\\.[\\w-]+)|(?:\\((?:[^()\\'\\\"]*(?:(?:\\'(?:[^\\']|(?:(?<!\\\\)\\\\\\'))*\\')|(?:\\\"(?:[^\\\"]|(?:(?<!\\\\)\\\\\\\"))*\\\")))*[^()]*\\))*)*)(?:(?:(?::\\s+)|(?<=\\)))(?:(?:(?:(?:#[\\w-]+)|(?:\\.[\\w-]+))|(?:(?:[#!]\\{[^}]*\\})|(?:\\w(?:(?:[\\w:-]+[\\w-])|(?:[\\w-]*)))))(?:(?:#[\\w-]+)|(?:\\.[\\w-]+)|(?:\\((?:[^()\\'\\\"]*(?:(?:\\'(?:[^\\']|(?:(?<!\\\\)\\\\\\'))*\\')|(?:\\\"(?:[^\\\"]|(?:(?<!\\\\)\\\\\\\"))*\\\")))*[^()]*\\))*)*))*)\\.$)(?:(?:(#[\\w-]+)|(\\.[\\w-]+))|((?:[#!]\\{[^}]*\\})|(?:\\w(?:(?:[\\w:-]+[\\w-])|(?:[\\w-]*)))))",
             "beginCaptures": {
                 "2": {
-                    "name": "entity.other.attribute-name.id.pug"
+                    "name": "entity.other.attribute-name.id.pupper"
                 },
                 "3": {
-                    "name": "entity.other.attribute-name.class.pug"
+                    "name": "entity.other.attribute-name.class.pupper"
                 },
                 "4": {
-                    "name": "meta.tag.other entity.name.tag.pug"
+                    "name": "meta.tag.other entity.name.tag.pupper"
                 }
             },
             "end": "^(?!(\\1\\s)|\\s*$)",
@@ -185,7 +218,7 @@
                 {
                     "begin": "^(?=.)",
                     "end": "$",
-                    "name": "text.block.pug",
+                    "name": "text.block.pupper",
                     "patterns": [
                         {
                             "include": "#inline_pug"
@@ -238,7 +271,7 @@
                 {
                     "begin": "\\|",
                     "end": "$",
-                    "name": "text.block.pipe.pug",
+                    "name": "text.block.pipe.pupper",
                     "comment": "Tag pipe text line.",
                     "patterns": [
                         {
@@ -323,21 +356,21 @@
         "blocks_and_includes": {
             "captures": {
                 "1": {
-                    "name": "storage.type.import.include.pug"
+                    "name": "storage.type.import.include.pupper"
                 },
                 "4": {
-                    "name": "variable.control.import.include.pug"
+                    "name": "variable.control.import.include.pupper"
                 }
             },
             "match": "(extends|include|yield|append|prepend|block( (append|prepend))?)\\s+(.*)$",
-            "name": "meta.first-class.pug",
+            "name": "meta.first-class.pupper",
             "comment": "Template blocks and includes."
         },
         "unbuffered_code": {
             "begin": "(-|(([a-zA-Z0-9_]+)\\s+=))",
             "beginCaptures": {
                 "3": {
-                    "name": "variable.parameter.javascript.embedded.pug"
+                    "name": "variable.parameter.javascript.embedded.pupper"
                 }
             },
             "end": "(?=\\])|(({\\s*)?$)",
@@ -359,10 +392,10 @@
             "match": "(mixin\\s+)([\\w-]+)(?:(\\()\\s*((?:[a-zA-Z_]\\w*\\s*)(?:,\\s*[a-zA-Z_]\\w*\\s*)*)(\\)))?$",
             "captures": {
                 "1": {
-                    "name": "storage.type.function.pug"
+                    "name": "storage.type.function.pupper"
                 },
                 "2": {
-                    "name": "meta.tag.other entity.name.function.pug"
+                    "name": "meta.tag.other entity.name.function.pupper"
                 },
                 "3": {
                     "name": "punctuation.definition.parameters.begin.js"
@@ -379,10 +412,10 @@
             "begin": "((?:mixin\\s+)|\\+)([\\w-]+)",
             "beginCaptures": {
                 "1": {
-                    "name": "storage.type.function.pug"
+                    "name": "storage.type.function.pupper"
                 },
                 "2": {
-                    "name": "meta.tag.other entity.name.function.pug"
+                    "name": "meta.tag.other entity.name.function.pupper"
                 }
             },
             "end": "(?!\\()|$",
@@ -390,7 +423,7 @@
                 {
                     "begin": "(?<!\\))\\(",
                     "end": "\\)",
-                    "name": "args.mixin.pug",
+                    "name": "args.mixin.pupper",
                     "patterns": [
                         {
                             "include": "#js_parens"
@@ -402,7 +435,7 @@
                             "match": "([^\\s(),=/]+)\\s*=\\s*",
                             "captures": {
                                 "1": {
-                                    "name": "meta.tag.other entity.other.attribute-name.tag.pug"
+                                    "name": "meta.tag.other entity.other.attribute-name.tag.pupper"
                                 }
                             }
                         },
@@ -420,17 +453,17 @@
             "begin": "(for|if|else if|else|each|until|while|unless|case)(\\s+|$)",
             "captures": {
                 "1": {
-                    "name": "storage.type.function.pug"
+                    "name": "storage.type.function.pupper"
                 }
             },
             "end": "$",
-            "name": "meta.control.flow.pug",
+            "name": "meta.control.flow.pupper",
             "comment": "Pug control flow.",
             "patterns": [
                 {
                     "begin": "",
                     "end": "$",
-                    "name": "js.embedded.control.flow.pug",
+                    "name": "js.embedded.control.flow.pupper",
                     "patterns": [
                         {
                             "include": "source.js"
@@ -442,14 +475,14 @@
         "case_when_paren": {
             "begin": "\\(",
             "end": "\\)",
-            "name": "js.when.control.flow.pug",
+            "name": "js.when.control.flow.pupper",
             "patterns": [
                 {
                     "include": "#case_when_paren"
                 },
                 {
                     "match": ":",
-                    "name": "invalid.illegal.name.tag.pug"
+                    "name": "invalid.illegal.name.tag.pupper"
                 },
                 {
                     "include": "source.js"
@@ -460,17 +493,17 @@
             "begin": "(default|when)((\\s+|(?=:))|$)",
             "captures": {
                 "1": {
-                    "name": "storage.type.function.pug"
+                    "name": "storage.type.function.pupper"
                 }
             },
             "end": "$",
-            "name": "meta.control.flow.pug",
+            "name": "meta.control.flow.pupper",
             "comment": "Pug case conditionals.",
             "patterns": [
                 {
                     "begin": "\\G(?!:)",
                     "end": "(?=:\\s+)|$",
-                    "name": "js.embedded.control.flow.pug",
+                    "name": "js.embedded.control.flow.pupper",
                     "patterns": [
                         {
                             "include": "#case_when_paren"
@@ -483,7 +516,7 @@
                 {
                     "begin": ":\\s+",
                     "end": "$",
-                    "name": "tag.case.control.flow.pug",
+                    "name": "tag.case.control.flow.pupper",
                     "patterns": [
                         {
                             "include": "#complete_tag"
@@ -510,7 +543,7 @@
                 },
                 {
                     "match": "(?<=:)\\w.*$",
-                    "name": "invalid.illegal.name.tag.pug"
+                    "name": "invalid.illegal.name.tag.pupper"
                 },
                 {
                     "include": "#tag_name"
@@ -531,10 +564,10 @@
                     "match": "((\\.)\\s+$)|((:)\\s*$)",
                     "captures": {
                         "2": {
-                            "name": "invalid.illegal.end.tag.pug"
+                            "name": "invalid.illegal.end.tag.pupper"
                         },
                         "4": {
-                            "name": "invalid.illegal.end.tag.pug"
+                            "name": "invalid.illegal.end.tag.pupper"
                         }
                     }
                 },
@@ -549,16 +582,16 @@
         "tag_name": {
             "begin": "([#!]\\{(?=.*?\\}))|(\\w(([\\w:-]+[\\w-])|([\\w-]*)))",
             "end": "(\\G(?<!\\5[^\\w-]))|\\}|$",
-            "name": "meta.tag.other entity.name.tag.pug",
+            "name": "meta.tag.other entity.name.tag.pupper",
             "patterns": [
                 {
                     "begin": "\\G(?<=\\{)",
                     "end": "(?=\\})",
-                    "name": "meta.tag.other entity.name.tag.pug",
+                    "name": "meta.tag.other entity.name.tag.pupper",
                     "patterns": [
                         {
                             "match": "{",
-                            "name": "invalid.illegal.tag.pug"
+                            "name": "invalid.illegal.tag.pupper"
                         },
                         {
                             "include": "source.js"
@@ -569,22 +602,22 @@
         },
         "tag_id": {
             "match": "#[\\w-]+",
-            "name": "entity.other.attribute-name.id.pug"
+            "name": "entity.other.attribute-name.id.pupper"
         },
         "tag_classes": {
             "match": "\\.([^\\w-])?[\\w-]*",
             "captures": {
                 "1": {
-                    "name": "invalid.illegal.tag.pug"
+                    "name": "invalid.illegal.tag.pupper"
                 }
             },
-            "name": "entity.other.attribute-name.class.pug"
+            "name": "entity.other.attribute-name.class.pupper"
         },
         "tag_attributes": {
             "begin": "(\\(\\s*)",
             "captures": {
                 "1": {
-                    "name": "constant.name.attribute.tag.pug"
+                    "name": "constant.name.attribute.tag.pupper"
                 }
             },
             "end": "(\\))",
@@ -601,7 +634,7 @@
                 },
                 {
                     "match": "!(?!=)",
-                    "name": "invalid.illegal.tag.pug"
+                    "name": "invalid.illegal.tag.pupper"
                 },
                 {
                     "begin": "=\\s*",
@@ -653,14 +686,14 @@
             "match": "([^\\s(),=/!]+)\\s*",
             "captures": {
                 "1": {
-                    "name": "entity.other.attribute-name.tag.pug"
+                    "name": "entity.other.attribute-name.tag.pupper"
                 }
             }
         },
         "tag_attribute_name_paren": {
             "begin": "\\(\\s*",
             "end": "\\)",
-            "name": "entity.other.attribute-name.tag.pug",
+            "name": "entity.other.attribute-name.tag.pupper",
             "patterns": [
                 {
                     "include": "#tag_attribute_name_paren"
@@ -674,7 +707,7 @@
             "begin": "(&attributes\\()",
             "captures": {
                 "1": {
-                    "name": "entity.name.function.pug"
+                    "name": "entity.name.function.pupper"
                 }
             },
             "end": "(\\))",
@@ -682,7 +715,7 @@
             "patterns": [
                 {
                     "match": "attributes(?=\\))",
-                    "name": "storage.type.keyword.pug"
+                    "name": "storage.type.keyword.pupper"
                 },
                 {
                     "include": "source.js"
@@ -750,14 +783,14 @@
             "begin": "(?<!\\\\)(#\\[)",
             "captures": {
                 "1": {
-                    "name": "entity.name.function.pug"
+                    "name": "entity.name.function.pupper"
                 },
                 "2": {
-                    "name": "entity.name.function.pug"
+                    "name": "entity.name.function.pupper"
                 }
             },
             "end": "(\\])",
-            "name": "inline.pug",
+            "name": "inline.pupper",
             "patterns": [
                 {
                     "include": "#inline_pug"
@@ -768,7 +801,7 @@
                 {
                     "begin": "(?<!\\])(?=[\\w.#])|(:\\s*)",
                     "end": "(?=\\]|(:.)|=|\\s)",
-                    "name": "tag.inline.pug",
+                    "name": "tag.inline.pupper",
                     "patterns": [
                         {
                             "include": "#tag_name"
@@ -790,7 +823,7 @@
                         },
                         {
                             "match": "\\[",
-                            "name": "invalid.illegal.tag.pug"
+                            "name": "invalid.illegal.tag.pupper"
                         }
                     ]
                 },
@@ -802,7 +835,7 @@
                 },
                 {
                     "match": "\\[",
-                    "name": "invalid.illegal.tag.pug"
+                    "name": "invalid.illegal.tag.pupper"
                 },
                 {
                     "include": "#inline_pug_text"
@@ -813,22 +846,22 @@
             "patterns": [
                 {
                     "match": "(&)([a-zA-Z0-9]+|#[0-9]+|#x[0-9a-fA-F]+)(;)",
-                    "name": "constant.character.entity.html.text.pug"
+                    "name": "constant.character.entity.html.text.pupper"
                 },
                 {
                     "match": "[<>&]",
-                    "name": "invalid.illegal.html_entity.text.pug"
+                    "name": "invalid.illegal.html_entity.text.pupper"
                 }
             ]
         },
         "interpolated_value": {
             "begin": "(?<!\\\\)[#!]\\{(?=.*?\\})",
             "end": "\\}",
-            "name": "string.interpolated.pug",
+            "name": "string.interpolated.pupper",
             "patterns": [
                 {
                     "match": "{",
-                    "name": "invalid.illegal.tag.pug"
+                    "name": "invalid.illegal.tag.pupper"
                 },
                 {
                     "include": "source.js"
@@ -837,7 +870,7 @@
         },
         "interpolated_error": {
             "match": "(?<!\\\\)[#!]\\{(?=[^}]*$)",
-            "name": "invalid.illegal.tag.pug"
+            "name": "invalid.illegal.tag.pupper"
         },
         "printed_expression": {
             "begin": "(!?\\=)\\s*",
@@ -860,11 +893,11 @@
         "string": {
             "begin": "(['\"])",
             "end": "(?<!\\\\)\\1",
-            "name": "string.quoted.pug",
+            "name": "string.quoted.pupper",
             "patterns": [
                 {
                     "match": "\\\\((x[0-9a-fA-F]{2})|(u[0-9]{4})|.)",
-                    "name": "constant.character.quoted.pug"
+                    "name": "constant.character.quoted.pupper"
                 },
                 {
                     "include": "#interpolated_value"

+ 16 - 8
packages/vscode-extension/pupper-js/test/test.pupper

@@ -3,7 +3,7 @@ style
         color: white;
     }
 
-style:sass
+style(lang="sass")
     .something2 {
         color: white;
 
@@ -17,12 +17,7 @@ script
         
     })
 
-script:ts
-    interface ITest {
-
-    }
-
-script:typescript
+script(lang="ts")
     enum ETest {
 
     }
@@ -34,10 +29,23 @@ template:html
 
 import(ImportedComponent from "test")
 
+data
+    number = 1
+    obj = {
+        itsAnObject: true
+    }
+
 template
     ImportedComponent
         template:slot-name
             |Filling a slot inside a component
 
     slot:slot-name
-        |Default slot content
+        |Default slot content
+
+implementation
+    when#something
+    listener#listenerName.eventThatIsListening.anotherEvent
+    event#customEvent
+
+    #onClick(something)

+ 2 - 2
test/templates/template.pupper

@@ -46,7 +46,7 @@ template
                         slot(name="slot")
                         slot(name="slot2")
 
-                footer.mastfoot.mt-auto
+                footer.mastfoot.mt-auto(p-listener="footerListener")
                     .inner
                         p
                             |Cover template for <a href="https://getbootstrap.com/">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>
@@ -88,4 +88,4 @@ implementation
         console.log("The component was mounted.");
 
     //- Listening to multiple events with one ID'ed listener.
-    event#listener.keyUp.keyPress
+    listener#footerListener.keyup.keypress