Skip to content

Commit

Permalink
feat: add new lint rule “no-entities-in-attributes”
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Kimbrell committed Dec 21, 2023
1 parent 536ee2e commit b060474
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 3 deletions.
4 changes: 4 additions & 0 deletions __tests__/examples/no-enties-in-attributes/document.html
@@ -0,0 +1,4 @@
<div style="font-family:
&quot;Franklin Gothic Medium&quot;,
&quot;Arial Narrow&quot;,
Arial, sans-serif;">
54 changes: 54 additions & 0 deletions __tests__/examples/no-enties-in-attributes/errors.js
@@ -0,0 +1,54 @@
module.exports = [
{
type: 'error',
col: 1,
evidence: "&quot;Franklin Gothic Medium&quot;,",
line: 2,
message: "Invalid entity [ &quot; ] encapsulated in the [ style ] attribute on line 2.",
raw: "&quot;",
rule: {
description: "No HTML entities within tag attributes.",
id: "no-enties-in-attributes",
link: "https://thecapsule.email/docs/codes/no-enties-in-attributes",
}
},
{
type: 'error',
col: 29,
evidence: "&quot;Franklin Gothic Medium&quot;,",
line: 2,
message: "Invalid entity [ &quot; ] encapsulated in the [ style ] attribute on line 2.",
raw: "&quot;",
rule: {
description: "No HTML entities within tag attributes.",
id: "no-enties-in-attributes",
link: "https://thecapsule.email/docs/codes/no-enties-in-attributes",
}
},
{
type: 'error',
col: 1,
evidence: "&quot;Arial Narrow&quot;,",
line: 3,
message: "Invalid entity [ &quot; ] encapsulated in the [ style ] attribute on line 3.",
raw: "&quot;",
rule: {
description: "No HTML entities within tag attributes.",
id: "no-enties-in-attributes",
link: "https://thecapsule.email/docs/codes/no-enties-in-attributes",
}
},
{
type: 'error',
col: 19,
evidence: "&quot;Arial Narrow&quot;,",
line: 3,
message: "Invalid entity [ &quot; ] encapsulated in the [ style ] attribute on line 3.",
raw: "&quot;",
rule: {
description: "No HTML entities within tag attributes.",
id: "no-enties-in-attributes",
link: "https://thecapsule.email/docs/codes/no-enties-in-attributes",
}
}
]
2 changes: 0 additions & 2 deletions __tests__/freemarker.spec.ts
@@ -1,8 +1,6 @@
import { parse } from '../src/parser';

test('that the following freemarker syntax will parse', () => {
console.log(parse('before<#if a></#if>after'));

expect(parse('before<#if a></#if>after')).toEqual([
'before',
'<#if a>',
Expand Down
6 changes: 6 additions & 0 deletions __tests__/rules.spec.ts
Expand Up @@ -95,6 +95,12 @@ describe('Rule: "valid-path-format"', () => {
});
});

describe('Rule: "no-enties-in-attributes"', () => {
it('Throws errors when HTML entities are inside of tag attributes.', () => {
rule('no-enties-in-attributes');
});
});

describe('Rule: "freemarker-tags"', () => {
it('Throws errors for invalid freemarker tags.', () => {
rule('freemarker-tags');
Expand Down
4 changes: 3 additions & 1 deletion demo/App.vue
Expand Up @@ -3,7 +3,9 @@ import { TextareaField } from '@vue-interface/textarea-field';
import { ref, watchEffect } from 'vue';
import { lint } from '../src/index';
const document = ref<string>(`asdasd<#if a></#if>`);
const document = ref<string>(`<div style="
aaaaasdasdasdasd
&quot;test&quot;">test</div>`);
const errors = ref<any[]>();
Expand Down
1 change: 1 addition & 0 deletions src/capsule.config.ts
Expand Up @@ -12,6 +12,7 @@ export default {
"img-src-required": true,
"invalid-attribute-char": true,
"nested-paragraphs": true,
"no-enties-in-attributes": true,
"spec-char-escape": true,
"src-not-empty": true,
"tag-pair": true,
Expand Down
2 changes: 2 additions & 0 deletions src/rules/index.ts
Expand Up @@ -10,6 +10,7 @@ import htmlValidChildrenOrder from './html-valid-children-order';
import imgSrcRequired from './img-src-required';
import invalidAttributeChar from './invalid-attribute-char';
import nestedParagraphs from './nested-paragraphs';
import noEntiesInAttributes from './no-enties-in-attributes';
import specialCharEscape from './special-char-escape';
import tagPair from './tag-pair';
import validPathFormat from './valid-path-format';
Expand All @@ -27,6 +28,7 @@ export default {
'img-src-required': imgSrcRequired,
'invalid-attribute-char': invalidAttributeChar,
'nested-paragraphs': nestedParagraphs,
'no-enties-in-attributes': noEntiesInAttributes,
'special-char-escape': specialCharEscape,
'tag-pair': tagPair,
'valid-path-format': validPathFormat,
Expand Down
50 changes: 50 additions & 0 deletions src/rules/no-enties-in-attributes.ts
@@ -0,0 +1,50 @@
import { Rule } from 'htmlhint/types';

const rule: Rule = {
id: 'no-enties-in-attributes',
description: 'No HTML entities within tag attributes.',
init(parser, reporter) {
parser.addListener('tagstart', event => {
event.attrs.forEach(({ value, name }) => {
value.split(/[\n\r\v]/gi).reduce(({ line, col }, str) => {
const matches = str.match(
/&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});/ig
);

if(!matches) {
return {
line: line + 1,
col: 0
};
}

let offset = 0;

for(const match of matches) {
const index = str.indexOf(match, offset);

reporter.error(
`Invalid entity [ ${match} ] encapsulated in the [ ${name} ] attribute on line ${line}.`,
line,
col + index + 1,
this,
match
);

offset = str.indexOf(match, offset) + match.length;
}

return {
line: line + 1,
col: 0
};
}, {
line: event.line,
col: event.raw.indexOf(value)
});
});
});
}
}

export default rule;

0 comments on commit b060474

Please sign in to comment.