...
 
Commits (19)
# html-validate changelog
# [1.7.0](https://gitlab.com/html-validate/html-validate/compare/v1.6.0...v1.7.0) (2019-09-11)
### Bug Fixes
- **parser:** fix conditional comments pushing elements into tree ([b26fe80](https://gitlab.com/html-validate/html-validate/commit/b26fe80)), closes [#51](https://gitlab.com/html-validate/html-validate/issues/51)
- **rules:** attr-case no longer reports duplicate errors for dynamic attributes ([c06ae67](https://gitlab.com/html-validate/html-validate/commit/c06ae67)), closes [#48](https://gitlab.com/html-validate/html-validate/issues/48)
### Features
- **location:** allow sliceLocation to wrap line/column ([cbd7796](https://gitlab.com/html-validate/html-validate/commit/cbd7796))
- **rules:** add PascalCase and camelCase styles for `attr-case` ([9e91f81](https://gitlab.com/html-validate/html-validate/commit/9e91f81)), closes [#49](https://gitlab.com/html-validate/html-validate/issues/49)
# [1.6.0](https://gitlab.com/html-validate/html-validate/compare/v1.5.1...v1.6.0) (2019-09-01)
### Bug Fixes
......
......@@ -3,7 +3,7 @@
exports[`docs/rules/no-conditional-comment.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 2,
"errorCount": 4,
"filePath": "inline",
"messages": Array [
Object {
......@@ -16,21 +16,43 @@ Array [
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 3,
"message": "Use of conditional comments are deprecated",
"offset": 53,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 5,
"message": "Use of conditional comments are deprecated",
"offset": 44,
"offset": 67,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 11,
},
Object {
"column": 1,
"context": undefined,
"line": 7,
"message": "Use of conditional comments are deprecated",
"offset": 123,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 10,
},
],
"source": "<![if IE 6]>
<style>
/* ... */
</style>
"source": "<!--[if IE]>
<p>You are using Internet Explorer.</p>
<![endif]-->
<![if !IE]>
<p>You are not using Internet Explorer.</p>
<![endif]>",
"warningCount": 0,
},
......
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<![if IE 6]>
<style>
/* ... */
</style>
markup["incorrect"] = `<!--[if IE]>
<p>You are using Internet Explorer.</p>
<![endif]-->
<![if !IE]>
<p>You are not using Internet Explorer.</p>
<![endif]>`;
describe("docs/rules/no-conditional-comment.md", () => {
......
......@@ -36,8 +36,10 @@ This rule takes an optional object:
### `style`
- `camelcase` requires all attribute names to be camelCase.
- `lowercase` requires all attribute names to be lowercase.
- `uppercase` requires all attribute names to be uppercase.
- `pascalcase` requires all attribute names to be PascalCase.
- `uppercase` requires all attribute names to be UPPERCASE.
### `ignoreForeign`
......
......@@ -17,9 +17,12 @@ Microsoft Internet Explorer previously supported using special HTML comments
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="no-conditional-comment">
<![if IE 6]>
<style>
/* ... */
</style>
<!--[if IE]>
<p>You are using Internet Explorer.</p>
<![endif]-->
<![if !IE]>
<p>You are not using Internet Explorer.</p>
<![endif]>
</validate>
This diff is collapsed.
{
"name": "html-validate",
"version": "1.6.0",
"version": "1.7.0",
"description": "html linter",
"keywords": [
"html",
......@@ -105,8 +105,8 @@
"minimist": "^1.2.0"
},
"devDependencies": {
"@babel/core": "7.5.5",
"@babel/preset-env": "7.5.5",
"@babel/core": "7.6.0",
"@babel/preset-env": "7.6.0",
"@semantic-release/changelog": "3.0.4",
"@semantic-release/exec": "3.3.6",
"@semantic-release/git": "7.0.16",
......@@ -120,8 +120,8 @@
"@types/json-merge-patch": "0.0.4",
"@types/minimist": "1.2.0",
"@types/node": "11.13.20",
"@typescript-eslint/eslint-plugin": "2.0.0",
"@typescript-eslint/parser": "2.0.0",
"@typescript-eslint/eslint-plugin": "2.2.0",
"@typescript-eslint/parser": "2.2.0",
"autoprefixer": "9.6.1",
"babelify": "10.0.0",
"bootstrap-sass": "3.4.1",
......@@ -129,12 +129,12 @@
"cssnano": "4.1.10",
"dgeni": "0.4.12",
"dgeni-packages": "0.28.1",
"eslint-config-prettier": "6.1.0",
"eslint-config-prettier": "6.3.0",
"eslint-config-sidvind": "1.3.2",
"eslint-plugin-array-func": "3.1.3",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "22.16.0",
"eslint-plugin-node": "9.2.0",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-node": "10.0.0",
"eslint-plugin-prettier": "3.1.0",
"eslint-plugin-security": "1.4.0",
"eslint-plugin-sonarjs": "0.4.0",
......@@ -142,12 +142,12 @@
"grunt": "1.0.4",
"grunt-browserify": "5.3.0",
"grunt-cli": "1.3.2",
"grunt-contrib-connect": "2.0.0",
"grunt-contrib-connect": "2.1.0",
"grunt-contrib-copy": "1.0.0",
"grunt-postcss": "0.9.0",
"grunt-sass": "3.1.0",
"highlight.js": "9.15.10",
"husky": "3.0.4",
"husky": "3.0.5",
"jest": "24.9.0",
"jest-diff": "24.9.0",
"jest-junit": "8.0.0",
......@@ -160,7 +160,7 @@
"serve-static": "1.14.1",
"strip-ansi": "5.2.0",
"ts-jest": "24.0.2",
"tslint": "5.19.0",
"tslint": "5.20.0",
"tslint-config-prettier": "1.18.0",
"typescript": "3.6.2"
},
......
......@@ -3,3 +3,110 @@
exports[`regression tests test-files/issues/issue27-disable-block.html 1`] = `Array []`;
exports[`regression tests test-files/issues/issue35-dynamic-values.html 1`] = `Array []`;
exports[`regression tests test-files/issues/issue51-cond-comment.html 1`] = `
Array [
Object {
"errorCount": 8,
"filePath": "test-files/issues/issue51-cond-comment.html",
"messages": Array [
Object {
"column": 1,
"context": undefined,
"line": 2,
"message": "Use of conditional comments are deprecated",
"offset": 16,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 17,
},
Object {
"column": 60,
"context": undefined,
"line": 2,
"message": "Use of conditional comments are deprecated",
"offset": 75,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 3,
"message": "Use of conditional comments are deprecated",
"offset": 88,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 14,
},
Object {
"column": 60,
"context": undefined,
"line": 3,
"message": "Use of conditional comments are deprecated",
"offset": 147,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 4,
"message": "Use of conditional comments are deprecated",
"offset": 160,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 14,
},
Object {
"column": 60,
"context": undefined,
"line": 4,
"message": "Use of conditional comments are deprecated",
"offset": 219,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 5,
"message": "Use of conditional comments are deprecated",
"offset": 232,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 17,
},
Object {
"column": 5,
"context": undefined,
"line": 7,
"message": "Use of conditional comments are deprecated",
"offset": 290,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
],
"source": "<!doctype html>
<!--[if lt IE 7]> <html class=\\"no-js ie6 oldie\\" lang=\\"en\\"> <![endif]-->
<!--[if IE 7]> <html class=\\"no-js ie7 oldie\\" lang=\\"en\\"> <![endif]-->
<!--[if IE 8]> <html class=\\"no-js ie8 oldie\\" lang=\\"en\\"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class=\\"no-js\\" lang=\\"en\\">
<!--<![endif]-->
<head>
<title>whatever</title>
</head>
<body>
<p>whatever</p>
</body>
</html>
",
"warningCount": 0,
},
]
`;
......@@ -68,4 +68,23 @@ describe("sliceLocation()", () => {
size: null,
});
});
it("should wrap line/column when newlines are present", () => {
const text = "foo\nbar baz\nspam";
(location as any).size = text.length;
expect(sliceLocation(location, 8, 11, text)).toEqual({
filename: "-",
offset: 8,
line: 2,
column: 5,
size: 3,
});
expect(sliceLocation(location, 12, 16, text)).toEqual({
filename: "-",
offset: 12,
line: 3,
column: 1,
size: 4,
});
});
});
......@@ -27,6 +27,14 @@ export interface Location {
readonly size?: number;
}
interface LocationRW {
filename: string;
offset: number;
line: number;
column: number;
size?: number;
}
function sliceSize(size: number, begin: number, end?: number): number {
if (typeof size !== "number") {
return size;
......@@ -43,26 +51,47 @@ function sliceSize(size: number, begin: number, end?: number): number {
/**
* Calculate a new location by offsetting this location.
*
* It is assumed there is no newlines anywhere between current location and
* the new.
* If the references text with newlines the wrap parameter must be set to
* properly calculate line and column information. If not given the text is
* assumed to contain no newlines.
*
* @param location Source location
* @param begin - Start location. Default is 0.
* @param end - End location. Default is size of location. Negative values are
* counted from end, e.g. `-2` means `size - 2`.
* @param wrap - If given, line/column is wrapped for each newline occuring
* before location end.
*/
export function sliceLocation(
location: Location,
begin: number,
end?: number
end?: number,
wrap?: string
): Location {
if (!location) return null;
const size = sliceSize(location.size, begin, end);
return {
const sliced: LocationRW = {
filename: location.filename,
offset: location.offset + begin,
line: location.line,
column: location.column + begin,
size,
};
/* if text content is provided try to find all newlines and modify line/column accordingly */
if (wrap) {
let index = -1;
const col = sliced.column;
do {
index = wrap.indexOf("\n", index + 1);
if (index >= 0 && index < begin) {
sliced.column = col - (index + 1);
sliced.line++;
} else {
break;
}
} while (true); // eslint-disable-line no-constant-condition
}
return sliced;
}
......@@ -658,56 +658,19 @@ describe("lexer", () => {
expect(token.next().done).toBeTruthy();
});
describe("browser conditional", () => {
it("downlevel-hidden", () => {
const token = lexer.tokenize(
inlineSource("<!--[if IE 6]>foo<![endif]-->")
);
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<!--[if IE 6]>", "if IE 6"],
});
expect(token.next()).toBeToken({ type: TokenType.TEXT, data: ["foo"] });
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<![endif]-->", "endif"],
});
expect(token.next()).toBeToken({ type: TokenType.EOF });
expect(token.next().done).toBeTruthy();
});
it("downlevel-reveal", () => {
const token = lexer.tokenize(inlineSource("<![if IE 6]>foo<![endif]>"));
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<![if IE 6]>", "if IE 6"],
});
expect(token.next()).toBeToken({ type: TokenType.TEXT, data: ["foo"] });
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<![endif]>", "endif"],
});
expect(token.next()).toBeToken({ type: TokenType.EOF });
/* downlevel reveal is a non-standard "tag", handled separately */
it("browser conditional downlevel-reveal", () => {
const token = lexer.tokenize(inlineSource("<![if IE 6]>foo<![endif]>"));
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<![if IE 6]>", "if IE 6"],
});
it("nested comment", () => {
const token = lexer.tokenize(
inlineSource("<!--[if IE 6]><!-- foo --><![endif]-->")
);
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<!--[if IE 6]>", "if IE 6"],
});
expect(token.next()).toBeToken({
type: TokenType.COMMENT,
data: ["<!-- foo -->", " foo "],
});
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<![endif]-->", "endif"],
});
expect(token.next()).toBeToken({ type: TokenType.EOF });
expect(token.next()).toBeToken({ type: TokenType.TEXT, data: ["foo"] });
expect(token.next()).toBeToken({
type: TokenType.CONDITIONAL,
data: ["<![endif]>", "endif"],
});
expect(token.next()).toBeToken({ type: TokenType.EOF });
});
});
});
......@@ -35,7 +35,7 @@ const MATCH_SCRIPT_DATA = /^[^]*?(?=<\/script)/;
const MATCH_SCRIPT_END = /^<(\/)(script)/;
const MATCH_DIRECTIVE = /^<!--\s\[html-validate-(.*?)]\s-->/;
const MATCH_COMMENT = /^<!--([^]*?)-->/;
const MATCH_CONDITIONAL = /^<!(?:--)?\[([^\]]*?)\](?:--)?>/;
const MATCH_CONDITIONAL = /^<!\[([^\]]*?)\]>/;
export class InvalidTokenError extends Error {
public location: Location;
......
import { Location } from "../context";
import { parseConditionalComment } from "./conditional-comment";
function mockLocation(text: string): Location {
return {
filename: "mock",
line: 1,
column: 1,
offset: 0,
size: text.length,
};
}
describe("parseConditionalComment()", () => {
it("should find conditions", () => {
const comment = "<!--[if gt IE 6]><!-->foo<!--<![endif]-->";
const location = mockLocation(comment);
const conditions = Array.from(parseConditionalComment(comment, location));
expect(conditions).toEqual([
{
expression: "if gt IE 6",
location: expect.objectContaining({
line: 1,
column: 1,
offset: 0,
size: 17,
}),
},
{
expression: "endif",
location: expect.objectContaining({
line: 1,
column: 30,
offset: 29,
size: 12,
}),
},
]);
});
});
import { Location, sliceLocation } from "../context";
export interface ConditionalComment {
expression: string;
location: Location;
}
const regexp = /<!(?:--)?\[(.*?)\](?:--)?>/g;
export function* parseConditionalComment(
comment: string,
commentLocation: Location
): IterableIterator<ConditionalComment> {
let match: RegExpExecArray;
// tslint:disable-next-line no-conditional-assignment
while ((match = regexp.exec(comment)) !== null) {
const expression = match[1];
const begin = match.index;
const end = begin + match[0].length;
const location = sliceLocation(commentLocation, begin, end, comment);
yield {
expression,
location,
};
}
}
......@@ -19,6 +19,7 @@ import {
import { Lexer, Token, TokenStream, TokenType } from "../lexer";
import { MetaTable } from "../meta";
import { AttributeData } from "./attribute-data";
import { parseConditionalComment } from "./conditional-comment";
/**
* Parse HTML document into a DOM tree.
......@@ -45,6 +46,7 @@ export class Parser {
* @param source - HTML markup.
* @returns DOM tree representing the HTML markup.
*/
// eslint-disable-next-line complexity
public parseHtml(source: string | Source): DOMTree {
if (typeof source === "string") {
source = {
......@@ -100,6 +102,10 @@ export class Parser {
});
break;
case TokenType.COMMENT:
this.consumeComment(token);
break;
case TokenType.DOCTYPE_OPEN:
this.consumeDoctype(token, tokenStream);
break;
......@@ -421,6 +427,24 @@ export class Parser {
});
}
/**
* Consumes comment token.
*
* Tries to find IE conditional comments and emits conditional token if found.
*/
protected consumeComment(token: Token): void {
const comment = token.data[0];
for (const conditional of parseConditionalComment(
comment,
token.location
)) {
this.trigger("conditional", {
condition: conditional.expression,
location: conditional.location,
});
}
}
/**
* Consumes doctype tokens. Emits doctype event.
*/
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rule attr-case configured with "camelcase" smoketest 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "test-files/rules/attr-case.html",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 2,
"message": "Attribute \\"ID\\" should be camelCase",
"offset": 23,
"ruleId": "attr-case",
"severity": 2,
"size": 2,
},
],
"source": "<p id=\\"foo\\">foo</p>
<p ID=\\"bar\\">bar</p>
<p clAss=\\"baz\\">baz</p>
",
"warningCount": 0,
},
]
`;
exports[`rule attr-case configured with "lowercase" smoketest 1`] = `
Array [
Object {
......@@ -36,6 +62,42 @@ Array [
]
`;
exports[`rule attr-case configured with "pascalcase" smoketest 1`] = `
Array [
Object {
"errorCount": 2,
"filePath": "test-files/rules/attr-case.html",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 1,
"message": "Attribute \\"id\\" should be PascalCase",
"offset": 3,
"ruleId": "attr-case",
"severity": 2,
"size": 2,
},
Object {
"column": 4,
"context": undefined,
"line": 3,
"message": "Attribute \\"clAss\\" should be PascalCase",
"offset": 43,
"ruleId": "attr-case",
"severity": 2,
"size": 5,
},
],
"source": "<p id=\\"foo\\">foo</p>
<p ID=\\"bar\\">bar</p>
<p clAss=\\"baz\\">baz</p>
",
"warningCount": 0,
},
]
`;
exports[`rule attr-case configured with "uppercase" smoketest 1`] = `
Array [
Object {
......
......@@ -10,7 +10,7 @@ Object {
exports[`rule no-conditional-comment smoketest 1`] = `
Array [
Object {
"errorCount": 4,
"errorCount": 24,
"filePath": "test-files/rules/no-conditional-comment.html",
"messages": Array [
Object {
......@@ -18,17 +18,77 @@ Array [
"context": undefined,
"line": 3,
"message": "Use of conditional comments are deprecated",
"offset": 17,
"offset": 43,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 52,
"context": undefined,
"line": 3,
"message": "Use of conditional comments are deprecated",
"offset": 94,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 4,
"message": "Use of conditional comments are deprecated",
"offset": 107,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 11,
},
Object {
"column": 55,
"context": undefined,
"line": 4,
"message": "Use of conditional comments are deprecated",
"offset": 161,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 10,
},
Object {
"column": 1,
"context": undefined,
"line": 6,
"message": "Use of conditional comments are deprecated",
"offset": 173,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 14,
},
Object {
"column": 53,
"context": undefined,
"line": 6,
"message": "Use of conditional comments are deprecated",
"offset": 225,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 7,
"message": "Use of conditional comments are deprecated",
"offset": 57,
"offset": 238,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 17,
},
Object {
"column": 53,
"context": undefined,
"line": 7,
"message": "Use of conditional comments are deprecated",
"offset": 290,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
......@@ -38,31 +98,179 @@ Array [
"context": undefined,
"line": 9,
"message": "Use of conditional comments are deprecated",
"offset": 71,
"offset": 304,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
"size": 18,
},
Object {
"column": 16,
"column": 56,
"context": undefined,
"line": 9,
"message": "Use of conditional comments are deprecated",
"offset": 86,
"offset": 359,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 10,
"message": "Use of conditional comments are deprecated",
"offset": 372,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 16,
},
Object {
"column": 57,
"context": undefined,
"line": 10,
"message": "Use of conditional comments are deprecated",
"offset": 428,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 11,
"message": "Use of conditional comments are deprecated",
"offset": 441,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 32,
},
Object {
"column": 69,
"context": undefined,
"line": 11,
"message": "Use of conditional comments are deprecated",
"offset": 509,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 12,
"message": "Use of conditional comments are deprecated",
"offset": 522,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 19,
},
Object {
"column": 76,
"context": undefined,
"line": 12,
"message": "Use of conditional comments are deprecated",
"offset": 597,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 14,
"message": "Use of conditional comments are deprecated",
"offset": 611,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 14,
},
Object {
"column": 57,
"context": undefined,
"line": 14,
"message": "Use of conditional comments are deprecated",
"offset": 667,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 1,
"context": undefined,
"line": 15,
"message": "Use of conditional comments are deprecated",
"offset": 680,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 13,
},
Object {
"column": 57,
"context": undefined,
"line": 15,
"message": "Use of conditional comments are deprecated",
"offset": 736,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 10,
},
Object {
"column": 1,
"context": undefined,
"line": 17,
"message": "Use of conditional comments are deprecated",
"offset": 748,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 14,
},
Object {
"column": 15,
"context": undefined,
"line": 17,
"message": "Use of conditional comments are deprecated",
"offset": 762,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
Object {
"column": 75,
"context": undefined,
"line": 17,
"message": "Use of conditional comments are deprecated",
"offset": 822,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 10,
},
Object {
"column": 85,
"context": undefined,
"line": 17,
"message": "Use of conditional comments are deprecated",
"offset": 832,
"ruleId": "no-conditional-comment",
"severity": 2,
"size": 12,
},
],
"source": "<!-- foobar -->
"source": "<!-- examples from MSDN documentation -->
<!--[if IE]><p>You are using Internet Explorer.</p><![endif]-->
<![if !IE]><p>You are not using Internet Explorer.</p><![endif]>
<!--[if IE 7]><p>Welcome to Internet Explorer 7!</p><![endif]-->
<!--[if !(IE 7)]><p>You are not using version 7.</p><![endif]-->
<!--[if gte IE 7]><p>You are using IE 7 or greater.</p><![endif]-->
<!--[if (IE 5)]><p>You are using IE 5 (any version).</p><![endif]-->
<!--[if (gte IE 5.5)&(lt IE 7)]><p>You are using IE 5.5 or IE 6.</p><![endif]-->
<!--[if lt IE 5.5]><p>Please upgrade your version of Internet Explorer.</p><![endif]-->
<!--[if IE 6]>
foo
<!-- spam -->
bar
<![endif]-->
<!--[if true]>You are using an <em>uplevel</em> browser.<![endif]-->
<![if false]>You are using a <em>downlevel</em> browser.<![endif]>
<![if IE 6]>foo<![endif]>
<!--[if true]><![if IE 7]><p>This nested comment is displayed in IE 7.</p><![endif]><![endif]-->
",
"warningCount": 0,
},
......
import HtmlValidate from "../htmlvalidate";
import "../matchers";
import { processAttribute } from "../transform/mocks/attribute";
describe("rule attr-case", () => {
let htmlvalidate: HtmlValidate;
......@@ -90,6 +91,92 @@ describe("rule attr-case", () => {
});
});
describe('configured with "pascalcase"', () => {
beforeAll(() => {
htmlvalidate = new HtmlValidate({
rules: { "attr-case": ["error", { style: "pascalcase" }] },
});
});
it("should not report error when attributes is PascalCase", () => {
const report = htmlvalidate.validateString('<div FooBar="baz"></div>');
expect(report).toBeValid();
});
it("should not report error when attributes is UPPERCASE", () => {
const report = htmlvalidate.validateString('<div FOOBAR="baz"></div>');
expect(report).toBeValid();
});
it("should report error when attributes is lowercase", () => {
const report = htmlvalidate.validateString('<div foobar="baz"></div>');
expect(report).toBeInvalid();
expect(report).toHaveError(
"attr-case",
'Attribute "foobar" should be PascalCase'
);
});
it("should report error when attributes is camelCase", () => {
const report = htmlvalidate.validateString('<div fooBar="baz"></div>');
expect(report).toBeInvalid();
expect(report).toHaveError(
"attr-case",
'Attribute "fooBar" should be PascalCase'
);
});
it("smoketest", () => {
const report = htmlvalidate.validateFile(
"test-files/rules/attr-case.html"
);
expect(report.results).toMatchSnapshot();
});
});
describe('configured with "camelcase"', () => {
beforeAll(() => {
htmlvalidate = new HtmlValidate({
rules: { "attr-case": ["error", { style: "camelcase" }] },
});
});
it("should not report error when attributes is camelCase", () => {
const report = htmlvalidate.validateString('<div fooBar="baz"></div>');
expect(report).toBeValid();
});
it("should not report error when attributes is lowercase", () => {
const report = htmlvalidate.validateString('<div foobar="baz"></div>');
expect(report).toBeValid();
});
it("should report error when attributes is UPPERCASE", () => {
const report = htmlvalidate.validateString('<div FOOBAR="baz"></div>');
expect(report).toBeInvalid();
expect(report).toHaveError(
"attr-case",
'Attribute "FOOBAR" should be camelCase'
);
});
it("should report error when attributes is PascalCase", () => {
const report = htmlvalidate.validateString('<div FooBar="baz"></div>');
expect(report).toBeInvalid();
expect(report).toHaveError(
"attr-case",
'Attribute "FooBar" should be camelCase'
);
});
it("smoketest", () => {
const report = htmlvalidate.validateFile(
"test-files/rules/attr-case.html"
);
expect(report.results).toMatchSnapshot();
});
});
describe('configured with "ignoreForeign" true', () => {
beforeAll(() => {
htmlvalidate = new HtmlValidate({
......@@ -120,6 +207,22 @@ describe("rule attr-case", () => {
});
});
it("should not report duplicate errors for dynamic attributes", () => {
htmlvalidate = new HtmlValidate({
rules: { "attr-case": "error" },
});
const report = htmlvalidate.validateString('<input dynamic-fooBar="foo">', {
processAttribute,
});
expect(report).toBeInvalid();
expect(report).toHaveErrors([
{
ruleId: "attr-case",
message: 'Attribute "dynamic-fooBar" should be lowercase',
},
]);
});
it("should throw error if configured with invalid value", () => {
htmlvalidate = new HtmlValidate({
rules: { "attr-case": ["error", { style: "foobar" }] },
......
......@@ -29,6 +29,13 @@ class AttrCase extends Rule {
return;
}
/* ignore case for dynamic attributes, the original attributes will be
* checked instead (this prevents duplicated errors for the same source
* attribute) */
if (event.originalAttribute) {
return;
}
const letters = event.key.replace(/[^a-z]+/gi, "");
if (!letters.match(this.pattern)) {
this.report(
......@@ -54,6 +61,10 @@ function parseStyle(style: string): [RegExp, string] {
return [/^[a-z]*$/, "lowercase"];
case "uppercase":
return [/^[A-Z]*$/, "uppercase"];
case "pascalcase":
return [/^[A-Z][A-Za-z]*$/, "PascalCase"];
case "camelcase":
return [/^[a-z][A-Za-z]*$/, "camelCase"];
default:
throw new Error(`Invalid style "${style}" for "attr-case" rule`);
}
......
......@@ -15,8 +15,8 @@ describe("rule no-conditional-comment", () => {
expect(report).toBeValid();
});
it("should report error when <![...]> is used", () => {
const report = htmlvalidate.validateString("<![if foo]>");
it("should report error when <!--[...]--> is used", () => {
const report = htmlvalidate.validateString("<!--[if foo]-->");
expect(report).toBeInvalid();
expect(report).toHaveError(
"no-conditional-comment",
......@@ -24,17 +24,8 @@ describe("rule no-conditional-comment", () => {
);
});
it("should report error when <!--[...]> is used", () => {
const report = htmlvalidate.validateString("<!--[if foo]>");
expect(report).toBeInvalid();
expect(report).toHaveError(
"no-conditional-comment",
"Use of conditional comments are deprecated"
);
});
it("should report error when <![...]--> is used", () => {
const report = htmlvalidate.validateString("<![endif]-->");
it("should report error when <![...]> is used", () => {
const report = htmlvalidate.validateString("<!-- foo <![if bar]> baz -->");
expect(report).toBeInvalid();
expect(report).toHaveError(
"no-conditional-comment",
......
<!doctype html>
<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie7 oldie" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie8 oldie" lang="en"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="en">
<!--<![endif]-->
<head>
<title>whatever</title>
</head>
<body>
<p>whatever</p>
</body>
</html>
<!-- foobar -->
<!-- examples from MSDN documentation -->
<!--[if IE 6]>
foo
<!-- spam -->
bar
<![endif]-->
<!--[if IE]><p>You are using Internet Explorer.</p><![endif]-->
<![if !IE]><p>You are not using Internet Explorer.</p><![endif]>
<![if IE 6]>foo<![endif]>
<!--[if IE 7]><p>Welcome to Internet Explorer 7!</p><![endif]-->
<!--[if !(IE 7)]><p>You are not using version 7.</p><![endif]-->
<!--[if gte IE 7]><p>You are using IE 7 or greater.</p><![endif]-->
<!--[if (IE 5)]><p>You are using IE 5 (any version).</p><![endif]-->
<!--[if (gte IE 5.5)&(lt IE 7)]><p>You are using IE 5.5 or IE 6.</p><![endif]-->
<!--[if lt IE 5.5]><p>Please upgrade your version of Internet Explorer.</p><![endif]-->
<!--[if true]>You are using an <em>uplevel</em> browser.<![endif]-->
<![if false]>You are using a <em>downlevel</em> browser.<![endif]>
<!--[if true]><![if IE 7]><p>This nested comment is displayed in IE 7.</p><![endif]><![endif]-->