From eae9a65111f40cbc82748bf9379baa72521b88d7 Mon Sep 17 00:00:00 2001
From: fisker <lionkay@gmail.com>
Date: Wed, 16 Apr 2025 23:47:54 +0800
Subject: [PATCH 1/2] feat: support Angular selectorless syntax

---
 .../.vscode/extensions.json                   |  8 ++
 .../angular-html-parser/.vscode/settings.json | 12 +++
 packages/angular-html-parser/src/index.ts     |  7 ++
 .../angular-html-parser/test/index_spec.ts    | 86 ++++++++++++++++++-
 4 files changed, 112 insertions(+), 1 deletion(-)
 create mode 100644 packages/angular-html-parser/.vscode/extensions.json
 create mode 100644 packages/angular-html-parser/.vscode/settings.json

diff --git a/packages/angular-html-parser/.vscode/extensions.json b/packages/angular-html-parser/.vscode/extensions.json
new file mode 100644
index 0000000000000..d85e4793e8b2a
--- /dev/null
+++ b/packages/angular-html-parser/.vscode/extensions.json
@@ -0,0 +1,8 @@
+{
+  "recommendations": [
+    "esbenp.prettier-vscode",
+    "editorconfig.editorconfig",
+    // "dbaeumer.vscode-eslint",
+    "streetsidesoftware.code-spell-checker",
+  ],
+}
diff --git a/packages/angular-html-parser/.vscode/settings.json b/packages/angular-html-parser/.vscode/settings.json
new file mode 100644
index 0000000000000..a215cd10444c4
--- /dev/null
+++ b/packages/angular-html-parser/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+  "[javascript][typescript][json][jsonc][markdown][yaml]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode",
+    "editor.formatOnSave": true,
+  },
+  // "[javascript][typescript]": {
+  //   "editor.codeActionsOnSave": {
+  //     "source.fixAll.eslint": "explicit",
+  //   },
+  // },
+  "prettier.requireConfig": true,
+}
diff --git a/packages/angular-html-parser/src/index.ts b/packages/angular-html-parser/src/index.ts
index ceef960444598..815fbd599d34f 100644
--- a/packages/angular-html-parser/src/index.ts
+++ b/packages/angular-html-parser/src/index.ts
@@ -49,6 +49,11 @@ export interface ParseOptions {
    * tokenize angular let declaration syntax
    */
   tokenizeAngularLetDeclaration?: boolean;
+
+  /**
+   * enable angular selectorless syntax
+   */
+  enableAngularSelectorlessSyntax?: boolean;
 }
 
 export function parse(
@@ -62,6 +67,7 @@ export function parse(
     getTagContentType,
     tokenizeAngularBlocks = false,
     tokenizeAngularLetDeclaration = false,
+    enableAngularSelectorlessSyntax = false,
   } = options;
   return getParser().parse(
     input,
@@ -73,6 +79,7 @@ export function parse(
       allowHtmComponentClosingTags,
       tokenizeBlocks: tokenizeAngularBlocks,
       tokenizeLet: tokenizeAngularLetDeclaration,
+      selectorlessEnabled: enableAngularSelectorlessSyntax,
     },
     isTagNameCaseSensitive,
     getTagContentType,
diff --git a/packages/angular-html-parser/test/index_spec.ts b/packages/angular-html-parser/test/index_spec.ts
index e6088bfb79893..81e87afcb6628 100644
--- a/packages/angular-html-parser/test/index_spec.ts
+++ b/packages/angular-html-parser/test/index_spec.ts
@@ -68,7 +68,7 @@ describe("AST format", () => {
     ]);
   });
 
-  it("should have `type` property when tokenizeBlocks is enabled", () => {
+  it("should support 'tokenizeAngularBlocks'", () => {
     const input = `@if (user.isHuman) { <p>Hello human</p> }`;
     const ast = parse(input, { tokenizeAngularBlocks: true });
     expect(ast.rootNodes).toEqual([
@@ -95,4 +95,88 @@ describe("AST format", () => {
       }),
     ]);
   });
+
+  it("should support 'tokenizeAngularLetDeclaration'", () => {
+    const input = `@let foo = 'bar';`;
+    const ast = parse(input, { tokenizeAngularLetDeclaration: true });
+    expect(ast.rootNodes).toEqual([
+      expect.objectContaining({
+        name: "foo",
+        type: "letDeclaration",
+        value: "'bar'",
+      }),
+    ]);
+  });
+
+  // https://github.com/angular/angular/pull/60724
+  it("should support 'enableAngularSelectorlessSyntax'", () => {
+    {
+      const ast = parse("<div @Dir></div>", {
+        enableAngularSelectorlessSyntax: true,
+      });
+      expect(ast.rootNodes).toEqual([
+        expect.objectContaining({
+          name: "div",
+          type: "element",
+          directives: [
+            expect.objectContaining({
+              name: "Dir",
+              type: "directive",
+            }),
+          ],
+        }),
+      ]);
+    }
+
+    {
+      const ast = parse("<MyComp>Hello</MyComp>", {
+        enableAngularSelectorlessSyntax: true,
+      });
+
+      expect(ast.rootNodes).toEqual([
+        expect.objectContaining({
+          fullName: "MyComp",
+          componentName: "MyComp",
+          type: "component",
+        }),
+      ]);
+    }
+
+    {
+      const ast = parse("<MyComp/>", { enableAngularSelectorlessSyntax: true });
+      expect(ast.rootNodes).toEqual([
+        expect.objectContaining({
+          fullName: "MyComp",
+          componentName: "MyComp",
+          type: "component",
+        }),
+      ]);
+    }
+
+    {
+      const ast = parse("<MyComp:button>Hello</MyComp:button>", {
+        enableAngularSelectorlessSyntax: true,
+      });
+      expect(ast.rootNodes).toEqual([
+        expect.objectContaining({
+          fullName: "MyComp:button",
+          componentName: "MyComp",
+          type: "component",
+        }),
+      ]);
+    }
+
+    {
+      const ast = parse("<MyComp:svg:title>Hello</MyComp:svg:title>", {
+        enableAngularSelectorlessSyntax: true,
+      });
+      expect(ast.rootNodes).toEqual([
+        expect.objectContaining({
+          fullName: "MyComp:svg:title",
+          componentName: "MyComp",
+          type: "component",
+        }),
+      ]);
+    }
+  });
 });

From e9db4f12931a526ed7e5b3014b12a50bf4027a4a Mon Sep 17 00:00:00 2001
From: fisker <lionkay@gmail.com>
Date: Wed, 16 Apr 2025 23:54:48 +0800
Subject: [PATCH 2/2] style: fix EOL

---
 packages/angular-html-parser/.vscode/extensions.json | 4 ++--
 packages/angular-html-parser/.vscode/settings.json   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/angular-html-parser/.vscode/extensions.json b/packages/angular-html-parser/.vscode/extensions.json
index d85e4793e8b2a..77e870d1b231a 100644
--- a/packages/angular-html-parser/.vscode/extensions.json
+++ b/packages/angular-html-parser/.vscode/extensions.json
@@ -3,6 +3,6 @@
     "esbenp.prettier-vscode",
     "editorconfig.editorconfig",
     // "dbaeumer.vscode-eslint",
-    "streetsidesoftware.code-spell-checker",
-  ],
+    "streetsidesoftware.code-spell-checker"
+  ]
 }
diff --git a/packages/angular-html-parser/.vscode/settings.json b/packages/angular-html-parser/.vscode/settings.json
index a215cd10444c4..707104dc663e6 100644
--- a/packages/angular-html-parser/.vscode/settings.json
+++ b/packages/angular-html-parser/.vscode/settings.json
@@ -1,12 +1,12 @@
 {
   "[javascript][typescript][json][jsonc][markdown][yaml]": {
     "editor.defaultFormatter": "esbenp.prettier-vscode",
-    "editor.formatOnSave": true,
+    "editor.formatOnSave": true
   },
   // "[javascript][typescript]": {
   //   "editor.codeActionsOnSave": {
   //     "source.fixAll.eslint": "explicit",
   //   },
   // },
-  "prettier.requireConfig": true,
+  "prettier.requireConfig": true
 }