Commits (35)
# Remember to keep in sync with .npmignore
*.tgz
tsconfig.tsbuildinfo
/.nyc_output/
/.tscache/
/dist
......
......@@ -131,6 +131,18 @@ Jest compat:
script:
- npm exec jest -- --no-coverage matchers tests/jest
Module:
stage: compatibility
dependencies: ["NPM", "Build"]
needs: ["NPM", "Build"]
parallel:
matrix:
- BUILD:
- "esm"
- "cjs"
script:
- node tests/integration/${BUILD}
Release:
stage: release
only:
......
# html-validate changelog
## [5.1.0](https://gitlab.com/html-validate/html-validate/compare/v5.0.2...v5.1.0) (2021-07-11)
### Features
- **lexer:** add attribute key-value delimiter to attribute value token ([0979798](https://gitlab.com/html-validate/html-validate/commit/0979798ac191af4a26667a282e3e554fee4450ac))
- **rules:** new rule `attr-delimiter` ([eb98461](https://gitlab.com/html-validate/html-validate/commit/eb9846155dbc42f860c31301373332624d35a3bd)), closes [#126](https://gitlab.com/html-validate/html-validate/issues/126)
- support .cjs configuration files ([cd458e3](https://gitlab.com/html-validate/html-validate/commit/cd458e3e16826700b93860507222d5af792204ef))
### Bug Fixes
- automatically find external dependencies ([62af8c5](https://gitlab.com/html-validate/html-validate/commit/62af8c5c97bdce0303ea8c5e00050014a959b08a))
- es build ([6b1cec5](https://gitlab.com/html-validate/html-validate/commit/6b1cec5b3321ddd8094483a50ce1cd686356142f))
- import semver correctly ([4ed8eac](https://gitlab.com/html-validate/html-validate/commit/4ed8eac2609dfd5411180bb6166ab8a8783dc53b))
- make prefer-button tag and attribute checks case-insensitive ([95e7748](https://gitlab.com/html-validate/html-validate/commit/95e774863a3df2197fb2cf7879e0e8cd83c3c4bd))
- only check value of type attribute in prefer-button rule ([2e46586](https://gitlab.com/html-validate/html-validate/commit/2e46586b5d69be6847128e58271d382cb7f46e90))
### [5.0.2](https://gitlab.com/html-validate/html-validate/compare/v5.0.1...v5.0.2) (2021-06-28)
### Bug Fixes
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/attr-delimiter.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/attr-delimiter.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 2,
"filePath": "inline",
"messages": Array [
Object {
"column": 12,
"context": undefined,
"line": 1,
"message": "Attribute value must not be delimited by whitespace",
"offset": 11,
"ruleId": "attr-delimiter",
"ruleUrl": "https://html-validate.org/rules/attr-delimiter.html",
"selector": null,
"severity": 2,
"size": 2,
},
Object {
"column": 12,
"context": undefined,
"line": 2,
"message": "Attribute value must not be delimited by whitespace",
"offset": 36,
"ruleId": "attr-delimiter",
"ruleUrl": "https://html-validate.org/rules/attr-delimiter.html",
"selector": null,
"severity": 2,
"size": 2,
},
],
"source": "<input name= \\"my-field\\">
<input name =\\"my-field\\">",
"warningCount": 0,
},
]
`;
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<input name= "my-field">
<input name ="my-field">`;
markup["correct"] = `<input name="my-field">`;
describe("docs/rules/attr-delimiter.md", () => {
it("inline validation: incorrect", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({"rules":{"attr-delimiter":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({"rules":{"attr-delimiter":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
---
docType: rule
name: attr-delimiter
summary: Disallow whitespace between attribute key and value
---
# Disallow whitespace between attribute key and value (`attr-delimiter`)
While technically allowed by the HTML5 specification this rule disallows the usage of whitespace separating the attribute key and value, i.e. before or after the `=` character.
Usage of whitespace in this context is typically a sign of typo or unintended behaviour.
For instance, consider the following markup:
<!-- prettier-ignore -->
```html
<input name= disabled>
```
As the HTML5 specification allows whitespace after `=` this is to be interpreted as `<input name="disabled">` which is has a completely different meaning than the developer probably intended.
This could have been generated from a templating langage where the value supposted to be bound to `name` was not set:
<!-- prettier-ignore -->
```html
<input name=<%= fieldName %> disabled>
```
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="attr-delimiter">
<input name= "my-field">
<input name ="my-field">
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="attr-delimiter">
<input name="my-field">
</validate>
......@@ -32,8 +32,11 @@ Run with:
Configuration can be added to:
- `.htmlvalidate.js`
- `.htmlvalidate.cjs`
- `.htmlvalidate.json`
ESM configuration files are not currently supported.
Configuration files will be searched from the target file and up until either no more parent folders exist or `"root": true` is found.
### `extends`
......@@ -153,11 +156,11 @@ Use a relative path to use a local script (use `<rootDir>` to refer to the path
By default, configuration is search in the file structure until the root directory (typically `/`) is found:
- `/home/user/project/src/.htmlvalidate.{js,json}`
- `/home/user/project/.htmlvalidate.{js,json}`
- `/home/user/.htmlvalidate.{js,json}`
- `/home/.htmlvalidate.{js,json}`
- `/.htmlvalidate.{js,json}`
- `/home/user/project/src/.htmlvalidate.{js,cjs,json}`
- `/home/user/project/.htmlvalidate.{js,cjs,json}`
- `/home/user/.htmlvalidate.{js,cjs,json}`
- `/home/.htmlvalidate.{js,cjs,json}`
- `/.htmlvalidate.{js,cjs,json}`
By setting the `root` property to `true` the search is stopped. This can be used
to prevent searching from outside the project directory or to use a specific
......@@ -173,8 +176,8 @@ For instance, if `/home/project/.htmlvalidate.json` contains:
Only the following files would be searched:
- `/home/user/project/src/.htmlvalidate.{js,json}`
- `/home/user/project/.htmlvalidate.{js,json}`
- `/home/user/project/src/.htmlvalidate.{js,cjs,json}`
- `/home/user/project/.htmlvalidate.{js,cjs,json}`
This also affects CLI `--config` and the API, e.g. when using `--config` with a
configuration using `"root": true` will prevent any additional files to be
......
This diff is collapsed.
{
"name": "html-validate",
"version": "5.0.2",
"version": "5.1.0",
"description": "Offline html5 validator",
"keywords": [
"html",
......@@ -49,11 +49,15 @@
"!**/*.snap",
"!**/*.spec.{js,ts,d.ts}"
],
"workspaces": [
"tests/integration/*"
],
"scripts": {
"prebuild": "tsc",
"prebuild": "tsc --build src",
"build": "rollup --config rollup.config.js",
"postbuild": "scripts/pkg",
"build:docs": "grunt docs",
"prepack": "npm run build",
"clean": "rm -rf dist public",
"compatibility": "scripts/compatibility.sh",
"debug": "node --inspect ./node_modules/.bin/jest --runInBand --watch --no-coverage",
......@@ -158,17 +162,17 @@
"@rollup/plugin-replace": "2.4.2",
"@rollup/plugin-typescript": "8.2.1",
"@rollup/plugin-virtual": "2.0.3",
"@types/babar": "0.2.0",
"@types/babel__code-frame": "7.0.2",
"@types/babar": "0.2.1",
"@types/babel__code-frame": "7.0.3",
"@types/estree": "0.0.47",
"@types/glob": "7.1.3",
"@types/inquirer": "7.3.2",
"@types/jest": "26.0.23",
"@types/glob": "7.1.4",
"@types/inquirer": "7.3.3",
"@types/jest": "26.0.24",
"@types/json-merge-patch": "0.0.5",
"@types/minimist": "1.2.1",
"@types/minimist": "1.2.2",
"@types/node": "11.15.54",
"@types/prompts": "2.0.13",
"@types/semver": "7.3.6",
"@types/prompts": "2.0.14",
"@types/semver": "7.3.7",
"autoprefixer": "10.2.6",
"babar": "0.2.0",
"babelify": "10.0.0",
......@@ -178,7 +182,7 @@
"dgeni": "0.4.14",
"dgeni-front-matter": "3.0.0",
"dgeni-packages": "0.29.1",
"eslint": "7.29.0",
"eslint": "7.30.0",
"font-awesome": "4.7.0",
"front-matter": "4.0.2",
"grunt": "1.4.1",
......@@ -187,10 +191,10 @@
"grunt-contrib-connect": "3.0.0",
"grunt-contrib-copy": "1.0.0",
"grunt-sass": "3.1.0",
"highlight.js": "11.0.1",
"husky": "6.0.0",
"jest": "27.0.5",
"jest-diff": "27.0.2",
"highlight.js": "11.1.0",
"husky": "7.0.1",
"jest": "27.0.6",
"jest-diff": "27.0.6",
"jquery": "3.6.0",
"lint-staged": "11.0.0",
"load-grunt-tasks": "5.1.0",
......@@ -199,22 +203,26 @@
"npm-pkg-lint": "1.4.0",
"postcss": "8.3.5",
"prettier": "2.3.2",
"rollup": "2.52.3",
"rollup": "2.53.0",
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-dts": "3.0.2",
"sass": "1.35.1",
"sass": "1.35.2",
"semantic-release": "17.4.4",
"serve-static": "1.14.1",
"stringmap": "0.2.2",
"ts-jest": "27.0.3",
"typescript": "4.3.4"
"typescript": "4.3.5"
},
"peerDependencies": {
"jest": "^24 || ^25 || ^26 || ^27"
"jest": "^24 || ^25 || ^26 || ^27",
"jest-diff": "^24 || ^25 || ^26 || ^27"
},
"peerDependenciesMeta": {
"jest": {
"optional": true
},
"jest-diff": {
"optional": true
}
},
"engines": {
......
import fs from "fs";
import path from "path";
import json from "@rollup/plugin-json";
import { builtinModules } from "module";
import json from "@rollup/plugin-json"; //native solution coming: https://nodejs.org/docs/latest/api/esm.html#esm_json_modules
import replace from "@rollup/plugin-replace";
import virtual from "@rollup/plugin-virtual";
import copy from "rollup-plugin-copy";
import dts from "rollup-plugin-dts";
import typescript from "@rollup/plugin-typescript";
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf-8"));
/**
* @typedef {import('rollup').RollupOptions} RollupOptions
*/
......@@ -34,25 +37,17 @@ const inputs = [...entrypoints, ...types];
/** @type {string[]} */
const external = [
/* nodejs */
"fs",
"path",
...builtinModules,
...builtinModules.map((name) => `node:${name}`), //spec: https://nodejs.org/docs/latest/api/esm.html#esm_node_imports
/* dependencies */
"@babel/code-frame",
"@html-validate/stylish",
"@sidvind/better-ajv-errors",
"ajv",
"deepmerge",
"glob",
"ignore",
"jest-diff",
"json-merge-patch",
"kleur",
"minimist",
"prompts",
/* npm dependencies */
...Object.keys(packageJson.dependencies),
...Object.keys(packageJson.peerDependencies),
];
const packageJson = fs.readFileSync(path.join(__dirname, "package.json"), "utf-8");
const jsonConfig = {
preferConst: true,
};
/**
* @param {string} id
......@@ -77,16 +72,35 @@ function manualChunks(id) {
return "core";
}
/**
* @param {string} format
* @returns {string}
*/
function generateResolved(format) {
if (format === "es") {
return `
import path from "path";
import { fileURLToPath } from "node:url";
import { createRequire } from "module";
export const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../");
export const legacyRequire = createRequire(import.meta.url);
export const distFolder = path.resolve(projectRoot, "dist/${format}");
`;
} else {
return `
import path from "path";
export const projectRoot = path.resolve(__dirname, "../../");
export const legacyRequire = require;
export const distFolder = path.resolve(projectRoot, "dist/${format}");
`;
}
}
/**
* @param {string} format
* @returns {RollupOptions[]}
*/
function build(format) {
const resolved = `
import path from "path";
export const projectRoot = path.resolve(__dirname, "../../");
export const distFolder = path.resolve(projectRoot, "dist/${format}");
`;
return [
{
input: entrypoints,
......@@ -100,23 +114,19 @@ function build(format) {
external,
plugins: [
virtual({
"package.json": packageJson,
"src/resolve": resolved,
"src/resolve": generateResolved(format),
}),
typescript({
tsconfig: "src/tsconfig.json",
outDir: `dist/${format}`,
declaration: false,
declarationDir: undefined,
}),
json(),
json(jsonConfig),
replace({
preventAssignment: true,
delimiters: ["", ""],
values: {
/**
* Fix the path from src/package.ts
*/
'"../package.json"': '"../../package.json"',
/**
* Replace __filename global with source filename relative to dist folder
*
......@@ -139,6 +149,17 @@ function build(format) {
chunkFileNames: "[name].d.ts",
},
plugins: [
replace({
preventAssignment: true,
delimiters: ["", ""],
values: {
/**
* Fix the path from dist/types/browser.d.ts
*/
'"../package.json"': '"../../package.json"',
},
}),
json(jsonConfig),
dts(),
copy({
verbose: true,
......
......@@ -15,4 +15,4 @@ export { TransformContext, Transformer, TemplateExtractor } from "./transform";
export { Plugin } from "./plugin";
export { Parser } from "./parser";
export { ruleExists } from "./utils";
export { version } from "./package";
export { version } from "../package.json";
import fs from "fs";
import path from "path";
import { Formatter, Report, Result, UserError, formatterFactory } from "..";
import { legacyRequire } from "../resolve";
type WrappedFormatter = (results: Result[]) => string;
......@@ -27,8 +28,7 @@ function loadFormatter(name: string): Formatter {
}
try {
/* eslint-disable-next-line import/no-dynamic-require */
return require(name);
return legacyRequire(name);
} catch (error) {
throw new UserError(`No formatter named "${name}"`, error);
}
......
......@@ -3,7 +3,7 @@ import path from "path";
import kleur from "kleur";
import minimist from "minimist";
import { TokenDump, SchemaValidationError, UserError, Report, Reporter, Result } from "..";
import * as pkg from "../package";
import { name, version, bugs as pkgBugs } from "../../package.json";
import { eventFormatter } from "./json";
import { CLI } from "./cli";
......@@ -123,8 +123,8 @@ function handleUnknownError(err: Error): void {
console.error(err);
}
console.groupEnd();
const bugUrl = `${pkg.bugs}?issuable_template=Bug`;
console.error(kleur.red(`This is a bug in ${pkg.name}-${pkg.version}.`));
const bugUrl = `${pkgBugs.url}?issuable_template=Bug`;
console.error(kleur.red(`This is a bug in ${name}-${version}.`));
console.error(
kleur.red(
[
......@@ -168,7 +168,7 @@ const argv: minimist.ParsedArgs = minimist(process.argv.slice(2), {
});
function showUsage(): void {
process.stdout.write(`${pkg.name}-${pkg.version}
process.stdout.write(`${name}-${version}
Usage: html-validate [OPTIONS] [FILENAME..] [DIR..]
Common options:
......@@ -204,7 +204,7 @@ e.g. "checkstyle=dist/html-validate.xml"
}
function showVersion(): void {
process.stdout.write(`${pkg.name}-${pkg.version}\n`);
process.stdout.write(`${name}-${version}\n`);
}
if (argv.stdin) {
......
......@@ -101,10 +101,13 @@ describe("ConfigLoader", () => {
mockFilenames: [
/* ConfigMock adds all visited filenames to this array */
path.resolve("/.htmlvalidate.js"),
path.resolve("/.htmlvalidate.cjs"),
path.resolve("/.htmlvalidate.json"),
path.resolve("/path/.htmlvalidate.js"),
path.resolve("/path/.htmlvalidate.cjs"),
path.resolve("/path/.htmlvalidate.json"),
path.resolve("/path/to/.htmlvalidate.js"),
path.resolve("/path/to/.htmlvalidate.cjs"),
path.resolve("/path/to/.htmlvalidate.json"),
],
})
......@@ -120,8 +123,10 @@ describe("ConfigLoader", () => {
mockFilenames: [
/* ConfigMock adds all visited filenames to this array */
path.resolve("/project/root/.htmlvalidate.js"),
path.resolve("/project/root/.htmlvalidate.cjs"),
path.resolve("/project/root/.htmlvalidate.json"),
path.resolve("/project/root/src/.htmlvalidate.js"),
path.resolve("/project/root/src/.htmlvalidate.cjs"),
path.resolve("/project/root/src/.htmlvalidate.json"),
],
})
......@@ -146,10 +151,13 @@ describe("ConfigLoader", () => {
mockFilenames: [
/* ConfigMock adds all visited filenames to this array */
path.resolve("/.htmlvalidate.js"),
path.resolve("/.htmlvalidate.cjs"),
path.resolve("/.htmlvalidate.json"),
path.resolve("/path/.htmlvalidate.js"),
path.resolve("/path/.htmlvalidate.cjs"),
path.resolve("/path/.htmlvalidate.json"),
path.resolve("/path/to/.htmlvalidate.js"),
path.resolve("/path/to/.htmlvalidate.cjs"),
path.resolve("/path/to/.htmlvalidate.json"),
],
})
......@@ -171,10 +179,13 @@ describe("ConfigLoader", () => {
mockFilenames: [
/* ConfigMock adds all visited filenames to this array */
path.resolve("/.htmlvalidate.js"),
path.resolve("/.htmlvalidate.cjs"),
path.resolve("/.htmlvalidate.json"),
path.resolve("/path/.htmlvalidate.js"),
path.resolve("/path/.htmlvalidate.cjs"),
path.resolve("/path/.htmlvalidate.json"),
path.resolve("/path/to/.htmlvalidate.js"),
path.resolve("/path/to/.htmlvalidate.cjs"),
path.resolve("/path/to/.htmlvalidate.json"),
],
})
......
......@@ -48,6 +48,7 @@ export class ConfigLoader {
*
* @param filename - Filename to get configuration for.
*/
// eslint-disable-next-line complexity, sonarjs/cognitive-complexity
public fromTarget(filename: string): Config | null {
if (filename === "inline") {
return null;
......@@ -63,18 +64,13 @@ export class ConfigLoader {
// eslint-disable-next-line no-constant-condition
while (true) {
const jsonFile = path.join(current, ".htmlvalidate.json");
if (fs.existsSync(jsonFile)) {
const local = this.configClass.fromFile(jsonFile);
found = true;
config = local.merge(config);
}
const jsFile = path.join(current, ".htmlvalidate.js");
if (fs.existsSync(jsFile)) {
const local = this.configClass.fromFile(jsFile);
found = true;
config = local.merge(config);
for (const potentialExtension of ["json", "cjs", "js"]) {
const filePath = path.join(current, `.htmlvalidate.${potentialExtension}`);
if (fs.existsSync(filePath)) {
const local = this.configClass.fromFile(filePath);
found = true;
config = local.merge(config);
}
}
/* stop if a configuration with "root" is set to true */
......
import fs from "fs";
import path from "path";
import Ajv from "ajv";
import ajvSchemaDraft from "ajv/lib/refs/json-schema-draft-06.json";
import deepmerge from "deepmerge";
import { Source } from "../context";
import { NestedError, SchemaValidationError } from "../error";
......@@ -10,7 +11,7 @@ import { Plugin } from "../plugin";
import schema from "../schema/config.json";
import { TransformContext, Transformer, TRANSFORMER_API } from "../transform";
import { requireUncached } from "../utils";
import { projectRoot } from "../resolve";
import { projectRoot, legacyRequire } from "../resolve";
import bundledRules from "../rules";
import { Rule } from "../rule";
import { ConfigData, RuleConfig, RuleOptions, TransformMap } from "./config-data";
......@@ -37,7 +38,7 @@ interface LoadedPlugin extends Plugin {
let rootDirCache: string | null = null;
const ajv = new Ajv({ strict: true, strictTuples: true, strictTypes: true });
ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-06.json"));
ajv.addMetaSchema(ajvSchemaDraft);
const validator = ajv.compile(schema);
......@@ -286,8 +287,7 @@ export class Config {
/* assume it is loadable with require() */
try {
// eslint-disable-next-line security/detect-non-literal-require, import/no-dynamic-require
metaTable.loadFromObject(require(entry));
metaTable.loadFromObject(legacyRequire(entry));
} catch (err) {
throw new ConfigError(`Failed to load elements from "${entry}": ${err.message}`, err);
}
......@@ -358,8 +358,7 @@ export class Config {
private loadPlugins(plugins: string[]): LoadedPlugin[] {
return plugins.map((moduleName: string) => {
try {
// eslint-disable-next-line security/detect-non-literal-require, import/no-dynamic-require
const plugin = require(moduleName.replace("<rootDir>", this.rootDir)) as LoadedPlugin;
const plugin = legacyRequire(moduleName.replace("<rootDir>", this.rootDir)) as LoadedPlugin;
plugin.name = plugin.name || moduleName;
plugin.originalName = moduleName;
return plugin;
......@@ -613,8 +612,7 @@ export class Config {
/* expand <rootDir> */
const moduleName = name.replace("<rootDir>", this.rootDir);
// eslint-disable-next-line security/detect-non-literal-require, import/no-dynamic-require
const fn = require(moduleName);
const fn = legacyRequire(moduleName);
/* sanity check */
if (typeof fn !== "function") {
......
......@@ -4,6 +4,7 @@ const config: ConfigData = {
rules: {
"aria-label-misuse": "error",
"attr-case": "error",
"attr-delimiter": "error",
"attr-quotes": "error",
"attr-spacing": "error",
"attribute-allowed-values": "error",
......
......@@ -283,7 +283,10 @@ describe("lexer", () => {
expect(token.next()).toBeToken({ type: TokenType.TAG_OPEN });
expect(token.next()).toBeToken({ type: TokenType.WHITESPACE });
expect(token.next()).toBeToken({ type: TokenType.ATTR_NAME });
expect(token.next()).toBeToken({ type: TokenType.ATTR_VALUE });
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: ['="baz"', "=", "baz", '"'],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
expect(token.next().done).toBeTruthy();
......@@ -297,7 +300,7 @@ describe("lexer", () => {
expect(token.next()).toBeToken({ type: TokenType.ATTR_NAME });
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: [' = "baz"', "baz", '"'],
data: [' = "baz"', " = ", "baz", '"'],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
......@@ -310,7 +313,10 @@ describe("lexer", () => {
expect(token.next()).toBeToken({ type: TokenType.TAG_OPEN });
expect(token.next()).toBeToken({ type: TokenType.WHITESPACE });
expect(token.next()).toBeToken({ type: TokenType.ATTR_NAME });
expect(token.next()).toBeToken({ type: TokenType.ATTR_VALUE });
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: ["='baz'", "=", "baz", "'"],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
expect(token.next().done).toBeTruthy();
......@@ -324,7 +330,7 @@ describe("lexer", () => {
expect(token.next()).toBeToken({ type: TokenType.ATTR_NAME });
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: [" = 'baz'", "baz", "'"],
data: [" = 'baz'", " = ", "baz", "'"],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
......@@ -340,7 +346,7 @@ describe("lexer", () => {
expect(token.next()).toBeToken({ type: TokenType.ATTR_NAME });
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: ["=baz", "baz"],
data: ["=baz", "=", "baz"],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
......@@ -358,7 +364,7 @@ describe("lexer", () => {
});
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: ["=5", "5"],
data: ["=5", "=", "5"],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
......@@ -373,7 +379,7 @@ describe("lexer", () => {
expect(token.next()).toBeToken({ type: TokenType.ATTR_NAME });
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: [" = baz", "baz"],
data: [" = baz", " = ", "baz"],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
......@@ -394,7 +400,7 @@ describe("lexer", () => {
});
expect(token.next()).toBeToken({
type: TokenType.ATTR_VALUE,
data: [`=${char}`, char],
data: [`=${char}`, "=", char],
});
expect(token.next()).toBeToken({ type: TokenType.TAG_CLOSE });
expect(token.next()).toBeToken({ type: TokenType.EOF });
......
......@@ -18,9 +18,9 @@ const MATCH_TEXT = /^[^]*?(?=(?:[ \t]*(?:\r\n|\r|\n)|<[^ ]|$))/;
const MATCH_TEMPLATING = /^(?:<%.*?%>|<\?.*?\?>|<\$.*?\$>)/;
const MATCH_TAG_LOOKAHEAD = /^[^]*?(?=<|$)/;
const MATCH_ATTR_START = /^([^\t\r\n\f \/><"'=]+)/; // https://www.w3.org/TR/html/syntax.html#elements-attributes
const MATCH_ATTR_SINGLE = /^\s*=\s*'([^']*?)(')/;
const MATCH_ATTR_DOUBLE = /^\s*=\s*"([^"]*?)(")/;
const MATCH_ATTR_UNQUOTED = /^\s*=\s*([^\t\r\n\f "'<>][^\t\r\n\f <>]*)/;
const MATCH_ATTR_SINGLE = /^(\s*=\s*)'([^']*?)(')/;
const MATCH_ATTR_DOUBLE = /^(\s*=\s*)"([^"]*?)(")/;
const MATCH_ATTR_UNQUOTED = /^(\s*=\s*)([^\t\r\n\f "'<>][^\t\r\n\f <>]*)/;
const MATCH_CDATA_BEGIN = /^<!\[CDATA\[/;
const MATCH_CDATA_END = /^[^]*?]]>/;
const MATCH_SCRIPT_DATA = /^[^]*?(?=<\/script)/;
......