diff --git a/packages/angular-html-parser/package.json b/packages/angular-html-parser/package.json index 3949d9c6d384..725932f3e299 100644 --- a/packages/angular-html-parser/package.json +++ b/packages/angular-html-parser/package.json @@ -23,11 +23,14 @@ "test": "vitest", "release": "release-it", "fix": "prettier . --write", - "lint": "prettier . --check" + "lint": "yarn lint:prettier && yarn lint:types", + "lint:prettier": "prettier . --check", + "lint:types": "tsc" }, "devDependencies": { "@types/node": "25.0.2", "@vitest/coverage-v8": "4.0.15", + "outdent": "0.8.0", "prettier": "3.7.4", "release-it": "19.1.0", "tsconfig-paths": "4.2.0", diff --git a/packages/angular-html-parser/src/index.ts b/packages/angular-html-parser/src/index.ts index 1684624cdceb..3bc402d45ed8 100644 --- a/packages/angular-html-parser/src/index.ts +++ b/packages/angular-html-parser/src/index.ts @@ -1,17 +1,9 @@ -import { HtmlParser } from "../../compiler/src/ml_parser/html_parser.js"; -import { TagContentType } from "../../compiler/src/ml_parser/tags.js"; -import { ParseTreeResult } from "../../compiler/src/ml_parser/parser.js"; +import { HtmlParser } from "../../compiler/src/ml_parser/html_parser.ts"; +import { XmlParser } from "../../compiler/src/ml_parser/xml_parser.ts"; +import type { TagContentType } from "../../compiler/src/ml_parser/tags.ts"; +import { ParseTreeResult as HtmlParseTreeResult } from "../../compiler/src/ml_parser/parser.ts"; -let parser: HtmlParser | null = null; - -const getParser = () => { - if (!parser) { - parser = new HtmlParser(); - } - return parser; -}; - -export interface ParseOptions { +export interface HtmlParseOptions { /** * any element can self close * @@ -56,10 +48,11 @@ export interface ParseOptions { enableAngularSelectorlessSyntax?: boolean; } -export function parse( +let htmlParser: HtmlParser; +export function parseHtml( input: string, - options: ParseOptions = {}, -): ParseTreeResult { + options: HtmlParseOptions = {}, +): HtmlParseTreeResult { const { canSelfClose = false, allowHtmComponentClosingTags = false, @@ -69,7 +62,9 @@ export function parse( tokenizeAngularLetDeclaration = false, enableAngularSelectorlessSyntax = false, } = options; - return getParser().parse( + htmlParser ??= new HtmlParser(); + + return htmlParser.parse( input, "angular-html-parser", { @@ -85,19 +80,30 @@ export function parse( ); } +let xmlParser: XmlParser; +export function parseXml(input: string) { + xmlParser ??= new XmlParser(); + + return xmlParser.parse(input, "angular-xml-parser"); +} + // For prettier -export { TagContentType }; +export { TagContentType } from "../../compiler/src/ml_parser/tags.ts"; export { RecursiveVisitor, visitAll, -} from "../../compiler/src/ml_parser/ast.js"; +} from "../../compiler/src/ml_parser/ast.ts"; export { ParseSourceSpan, ParseLocation, ParseSourceFile, -} from "../../compiler/src/parse_util.js"; -export { getHtmlTagDefinition } from "../../compiler/src/ml_parser/html_tags.js"; +} from "../../compiler/src/parse_util.ts"; +export { getHtmlTagDefinition } from "../../compiler/src/ml_parser/html_tags.ts"; // Types -export type { ParseTreeResult } from "../../compiler/src/ml_parser/parser.js"; -export type * as Ast from "../../compiler/src/ml_parser/ast.js"; +export type { ParseTreeResult } from "../../compiler/src/ml_parser/parser.ts"; +export type * as Ast from "../../compiler/src/ml_parser/ast.ts"; + +// Remove these alias in next major release +export type { HtmlParseOptions as ParseOptions }; +export { parseHtml as parse }; diff --git a/packages/angular-html-parser/test/index_spec.ts b/packages/angular-html-parser/test/index_spec.ts index c6d9ce20001a..41da232a1905 100644 --- a/packages/angular-html-parser/test/index_spec.ts +++ b/packages/angular-html-parser/test/index_spec.ts @@ -1,6 +1,7 @@ -import { parse, TagContentType } from "../src/index.js"; -import { humanizeDom } from "../../compiler/test/ml_parser/ast_spec_utils.js"; -import * as html from "../../compiler/src/ml_parser/ast.js"; +import { describe, it, expect } from "vitest"; +import { parse, TagContentType } from "../src/index.ts"; +import { humanizeDom } from "../../compiler/test/ml_parser/ast_spec_utils.ts"; +import * as ast from "../../compiler/src/ml_parser/ast.ts"; describe("options", () => { describe("getTagContentType", () => { @@ -35,17 +36,17 @@ describe("options", () => { } }; expect(humanizeDom(parse(input, { getTagContentType }))).toEqual([ - [html.Element, "template", 0], - [html.Element, "MyComponent", 1], - [html.Element, "template", 2], - [html.Attribute, "#content", ""], - [html.Text, "text", 3, ["text"]], - [html.Element, "template", 0], - [html.Attribute, "lang", "something-else", ["something-else"]], - [html.Text, "
Hello human
}`; - const ast = parse(input, { tokenizeAngularBlocks: true }); - expect(ast.rootNodes).toEqual([ + const result = parse(input, { tokenizeAngularBlocks: true }); + expect(result.rootNodes).toEqual([ expect.objectContaining({ name: "if", kind: "block", @@ -122,43 +123,43 @@ describe("AST format", () => { } } `; - const ast = parse(input, { tokenizeAngularBlocks: true }); - expect(humanizeDom(ast)).toEqual([ - [html.Text, "\n", 0, ["\n"]], - [html.Block, "switch", 0], - [html.BlockParameter, "case"], - [html.Text, "\n ", 1, ["\n "]], - [html.Block, "case", 1], - [html.BlockParameter, "0"], - [html.Block, "case", 1], - [html.BlockParameter, "1"], - [html.Text, "\n ", 2, ["\n "]], - [html.Element, "div", 2], - [html.Text, "case 0 or 1", 3, ["case 0 or 1"]], - [html.Text, "\n ", 2, ["\n "]], - [html.Text, "\n ", 1, ["\n "]], - [html.Block, "case", 1], - [html.BlockParameter, "2"], - [html.Text, "\n ", 2, ["\n "]], - [html.Element, "div", 2], - [html.Text, "case 2", 3, ["case 2"]], - [html.Text, "\n ", 2, ["\n "]], - [html.Text, "\n ", 1, ["\n "]], - [html.Block, "default", 1], - [html.Text, "\n ", 2, ["\n "]], - [html.Element, "div", 2], - [html.Text, "default", 3, ["default"]], - [html.Text, "\n ", 2, ["\n "]], - [html.Text, "\n", 1, ["\n"]], - [html.Text, "\n ", 0, ["\n "]], + const result = parse(input, { tokenizeAngularBlocks: true }); + expect(humanizeDom(result)).toEqual([ + [ast.Text, "\n", 0, ["\n"]], + [ast.Block, "switch", 0], + [ast.BlockParameter, "case"], + [ast.Text, "\n ", 1, ["\n "]], + [ast.Block, "case", 1], + [ast.BlockParameter, "0"], + [ast.Block, "case", 1], + [ast.BlockParameter, "1"], + [ast.Text, "\n ", 2, ["\n "]], + [ast.Element, "div", 2], + [ast.Text, "case 0 or 1", 3, ["case 0 or 1"]], + [ast.Text, "\n ", 2, ["\n "]], + [ast.Text, "\n ", 1, ["\n "]], + [ast.Block, "case", 1], + [ast.BlockParameter, "2"], + [ast.Text, "\n ", 2, ["\n "]], + [ast.Element, "div", 2], + [ast.Text, "case 2", 3, ["case 2"]], + [ast.Text, "\n ", 2, ["\n "]], + [ast.Text, "\n ", 1, ["\n "]], + [ast.Block, "default", 1], + [ast.Text, "\n ", 2, ["\n "]], + [ast.Element, "div", 2], + [ast.Text, "default", 3, ["default"]], + [ast.Text, "\n ", 2, ["\n "]], + [ast.Text, "\n", 1, ["\n"]], + [ast.Text, "\n ", 0, ["\n "]], ]); } }); it("should support 'tokenizeAngularLetDeclaration'", () => { const input = `@let foo = 'bar';`; - const ast = parse(input, { tokenizeAngularLetDeclaration: true }); - expect(ast.rootNodes).toEqual([ + const result = parse(input, { tokenizeAngularLetDeclaration: true }); + expect(result.rootNodes).toEqual([ expect.objectContaining({ name: "foo", kind: "letDeclaration", @@ -170,10 +171,10 @@ describe("AST format", () => { // https://github.com/angular/angular/pull/60724 it("should support 'enableAngularSelectorlessSyntax'", () => { { - const ast = parse("", { + const result = parse("", { enableAngularSelectorlessSyntax: true, }); - expect(ast.rootNodes).toEqual([ + expect(result.rootNodes).toEqual([ expect.objectContaining({ name: "div", kind: "element", @@ -188,11 +189,11 @@ describe("AST format", () => { } { - const ast = parse("