diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 3b9bd64..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "extends": "eslint:recommended",
- "parserOptions": {
- "sourceType": "module",
- "ecmaVersion": 2020
- },
- "env": {
- "es6": true,
- "node": true,
- "browser": true
- },
- "rules": {
- "no-cond-assign": 0,
- "no-constant-condition": 0,
- "no-unexpected-multiline": 0,
- "comma-dangle": ["error", "never"],
- "semi": [2, "always"]
- }
-}
diff --git a/.github/eslint.json b/.github/eslint.json
deleted file mode 100644
index c280fdb..0000000
--- a/.github/eslint.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "problemMatcher": [
- {
- "owner": "eslint-compact",
- "pattern": [
- {
- "regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$",
- "file": 1,
- "line": 2,
- "column": 3,
- "severity": 4,
- "message": 5,
- "code": 6
- }
- ]
- }
- ]
-}
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
deleted file mode 100644
index 7e5ee59..0000000
--- a/.github/workflows/node.js.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-# https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
-
-name: Node.js CI
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- strategy:
- matrix:
- node-version: [14.x]
-
- steps:
- - uses: actions/checkout@v2
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v1
- with:
- node-version: ${{ matrix.node-version }}
- - run: yarn --frozen-lockfile
- - run: |
- echo ::add-matcher::.github/eslint.json
- yarn run eslint src test --format=compact
- - run: yarn test
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..4a05314
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,26 @@
+name: Publish
+
+on:
+ workflow_dispatch: {}
+ release:
+ types: [published]
+
+permissions:
+ id-token: write
+ contents: read
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v5
+ with:
+ version: 10
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install
+ - run: pnpm run test
+ - run: npm publish
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..a0033d0
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,23 @@
+name: Test
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v5
+ with:
+ version: 10
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install
+ - run: pnpm run test
+ - run: pnpm run lint
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..00e5f81
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+src/index.ts
+test/snapshots.test.js
diff --git a/LICENSE b/LICENSE
index 0140869..3e5fda7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2019-2021 Observable, Inc.
+Copyright 2019-2026 Observable, Inc.
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
diff --git a/README.md b/README.md
index c1590fb..df8ef65 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Hypertext Literal is a tagged template literal for HTML which interpolates values *based on context*, allowing automatic escaping and the interpolation of non-serializable values, such as event listeners, style objects, and other DOM nodes. It is inspired by [lit-html](https://lit-html.polymer-project.org/) and [HTM](https://github.com/developit/htm), and references the fantastically precise [HTML5 spec](https://html.spec.whatwg.org/multipage/parsing.html#tokenization).
-Hypertext Literal is open-sourced under the permissive ISC license, small (2KB), has no dependencies, and is available [on npm](https://www.npmjs.com/package/htl). To install:
+Hypertext Literal is open-sourced under the permissive ISC license, is small, has no dependencies, and is available [on npm](https://www.npmjs.com/package/htl). To install:
```
npm install htl
@@ -78,7 +78,7 @@ html`This text has color.`
In cases where it is not possible to interpolate safely, namely with script and style elements where the interpolated value contains the corresponding end tag, an error is thrown.
```js
-html`"}` // Error: unsafe raw text
+html`"}` // Error: cannot interpolate into "}` // Error: cannot interpolate into `, "cannot interpolate in state 4");
+});
+
+it("interpolating into a partial end tag name is not allowed", () => {
+ assert.throws(() => html``, "cannot interpolate in state 29");
+});
+
+it("interpolating into a greater-than sign into attributes is not allowed", () => {
+ assert.throws(() => html`"}>`, "interpolated attribute name contains bare '>'");
+ assert.throws(() => html`"}>`, "interpolated attribute name contains bare '>'");
+});
diff --git a/test/invalid-empty-unquoted-attribute-value-test.js b/test/invalid-empty-unquoted-attribute-value-test.js
deleted file mode 100644
index 7b3ee3d..0000000
--- a/test/invalid-empty-unquoted-attribute-value-test.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import assert from "assert";
-import {html} from "../src/index.js";
-import it from "./jsdom.js";
-
-it("interpolating an empty string with a suffix is not allowed", () => {
- assert.throws(() => html`