Skip to content

Commit

Permalink
improve class and id literal rules
Browse files Browse the repository at this point in the history
allow class and id literal to allow/ignore certain patterns

Closes gh-140 - pull request
Fixes #118 and #83 - issue
  • Loading branch information
lightbringer1991 committed Nov 9, 2017
1 parent b7c166f commit e7ab2f1
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 21 deletions.
44 changes: 41 additions & 3 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ p: strong text
table: tr: td text
```

# disallowClassAttributeWithStaticValue: `true`
# disallowClassAttributeWithStaticValue: `true` | `object`

Prefer class literals over `class` attributes with static values.

## e.g.: `true`
```pug
//- Invalid
span(class='foo')
Expand All @@ -73,6 +74,24 @@ span(class='foo')
span.foo
```

## e.g. `{ denyTokens: ['::'] }`
```pug
//- Invalid
span(class='sth::sth-else')
//- Valid
span(class='sth:sth-else')
```

## e.g. `{ allowTokens: ['{'] }`
```pug
//- Invalid
span(class='sth::sth-else')
//- Valid
span(class='{{::testVariable}}')
```

# disallowClassLiteralsBeforeAttributes: `true`

All attribute blocks must be written before any class literals.
Expand Down Expand Up @@ -134,16 +153,35 @@ Pug must not contain any HTML text.
p this is <strong>html</strong> text
```

# disallowIdAttributeWithStaticValue: `true`
# disallowIdAttributeWithStaticValue: `true` | `object`

Prefer ID literals over `id` attributes with static values.

## e.g.: `true`
```pug
//- Invalid
span(id='foo')
//- Valid
span#id
span#foo
```

## e.g. `{ denyTokens: ['::'] }`
```pug
//- Invalid
span(id='sth::sth-else')
//- Valid
span(id='sth:sth-else')
```

## e.g. `{ allowTokens: ['{'] }`
```pug
//- Invalid
span(id='sth::sth-else')
//- Valid
span(id='{{::testVariable}}')
```

# disallowIdLiteralsBeforeAttributes: `true`
Expand Down
26 changes: 25 additions & 1 deletion lib/pug-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var PugFile = function (filename, source) {
};

PugFile.prototype = {
addErrorForAllStaticAttributeValues: function (name, errors, message) {
addErrorForAllStaticAttributeValues: function (name, ruleOptions, errors, message) {
this.iterateTokensByFilter(function (token) {
return token.type === 'attribute' && token.name.toLowerCase() === name.toLowerCase();
}, function (token) {
Expand All @@ -53,6 +53,30 @@ PugFile.prototype = {
};
var compiled = attrs([token], options);

if (typeof ruleOptions === 'object') {
var i;

// check the list of allowCharacters
if ('allowTokens' in ruleOptions) {
for (i = 0; i < ruleOptions.allowTokens.length; i++) {
if (token.val.includes(ruleOptions.allowTokens[i])) {
return;
}
}
}

// check the list of denyTokens
if ('denyTokens' in ruleOptions) {
for (i = 0; i < ruleOptions.denyTokens.length; i++) {
var col = token.val.indexOf(ruleOptions.denyTokens[i]);
if (col !== -1) {
errors.add(message, token.line, col);
return;
}
}
}
}

if (!/^attr\(.*\)$/g.exec(compiled)) {
errors.add(message, token.line, token.col);
}
Expand Down
38 changes: 34 additions & 4 deletions lib/rules/disallow-class-attribute-with-static-value.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
// # disallowClassAttributeWithStaticValue: `true`
// # disallowClassAttributeWithStaticValue: `true` | `object`
//
// Prefer class literals over `class` attributes with static values.
//
// ## e.g.: `true`
// ```pug
// //- Invalid
// span(class='foo')
//
// //- Valid
// span.foo
// ```
//
// ## e.g. `{ denyTokens: ['::'] }`
// ```pug
// //- Invalid
// span(class='sth::sth-else')
//
// //- Valid
// span(class='sth:sth-else')
// ```
//
// ## e.g. `{ allowTokens: ['{'] }`
// ```pug
// //- Invalid
// span(class='sth::sth-else')
//
// //- Valid
// span(class='{{::testVariable}}')
// ```

var utils = require('../utils');

Expand All @@ -18,14 +37,25 @@ module.exports.prototype = {
name: 'disallowClassAttributeWithStaticValue',

schema: {
enum: [null, true]
anyOf: [
{
enum: [null, true]
},
{
type: 'object',
properties: {
allowTokens: {type: 'array'},
denyTokens: {type: 'array'}
}
}
]
},

configure: function (options) {
utils.validateTrueOptions(this.name, options);
this._options = utils.validateStaticValueOptions(this.name, options);
},

lint: function (file, errors) {
file.addErrorForAllStaticAttributeValues('class', errors, 'Static attribute "class" must be written as class literal');
file.addErrorForAllStaticAttributeValues('class', this._options, errors, 'Static attribute "class" must be written as class literal');
}
};
40 changes: 35 additions & 5 deletions lib/rules/disallow-id-attribute-with-static-value.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
// # disallowIdAttributeWithStaticValue: `true`
// # disallowIdAttributeWithStaticValue: `true` | `object`
//
// Prefer ID literals over `id` attributes with static values.
//
// ## e.g.: `true`
// ```pug
// //- Invalid
// span(id='foo')
//
// //- Valid
// span#id
// span#foo
// ```
//
// ## e.g. `{ denyTokens: ['::'] }`
// ```pug
// //- Invalid
// span(id='sth::sth-else')
//
// //- Valid
// span(id='sth:sth-else')
// ```
//
// ## e.g. `{ allowTokens: ['{'] }`
// ```pug
// //- Invalid
// span(id='sth::sth-else')
//
// //- Valid
// span(id='{{::testVariable}}')
// ```

var utils = require('../utils');
Expand All @@ -18,14 +37,25 @@ module.exports.prototype = {
name: 'disallowIdAttributeWithStaticValue',

schema: {
enum: [null, true]
anyOf: [
{
enum: [null, true]
},
{
type: 'object',
properties: {
allowTokens: {type: 'array'},
denyTokens: {type: 'array'}
}
}
]
},

configure: function (options) {
utils.validateTrueOptions(this.name, options);
this._options = utils.validateStaticValueOptions(this.name, options);
},

lint: function (file, errors) {
file.addErrorForAllStaticAttributeValues('id', errors, 'Static attribute "id" must be written as ID literal');
file.addErrorForAllStaticAttributeValues('id', this._options, errors, 'Static attribute "id" must be written as ID literal');
}
};
19 changes: 19 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ exports.validateCodeOperatorOptions = function (name, options) {
return options;
};

exports.validStaticValueOptions = [
'allowTokens',
'denyTokens'
];

exports.validateStaticValueOptions = function (name, options) {
if (options === null || options === true) {
return options;
}
assert(typeof options === 'object', name + ' option must be "true" or an object');

var optionKeys = Object.keys(options);
for (var i = 0; i < optionKeys.length; i++) {
var key = optionKeys[i];
assert.notEqual(exports.validStaticValueOptions.indexOf(key), -1, 'Invalid option ' + key);
}
return options;
};

exports.codeOperatorTypes = [
'-',
'=',
Expand Down
46 changes: 38 additions & 8 deletions schemas/pug-lintrc-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,27 @@
"documentation": "# disallowBlockExpansion: `true`\n\nPug must not contain any block expansion operators.\n\n```pug\n//- Invalid\np: strong text\ntable: tr: td text\n```\n"
},
"disallowClassAttributeWithStaticValue": {
"enum": [
null,
true
"anyOf": [
{
"enum": [
null,
true
]
},
{
"type": "object",
"properties": {
"allowTokens": {
"type": "array"
},
"denyTokens": {
"type": "array"
}
}
}
],
"description": "Prefer class literals over `class` attributes with static values.",
"documentation": "# disallowClassAttributeWithStaticValue: `true`\n\nPrefer class literals over `class` attributes with static values.\n\n```pug\n//- Invalid\nspan(class='foo')\n\n//- Valid\nspan.foo\n```\n"
"documentation": "# disallowClassAttributeWithStaticValue: `true` | `object`\n\nPrefer class literals over `class` attributes with static values.\n\n## e.g.: `true`\n```pug\n//- Invalid\nspan(class='foo')\n\n//- Valid\nspan.foo\n```\n\n## e.g. `{ denyTokens: ['::'] }`\n```pug\n//- Invalid\nspan(class='sth::sth-else')\n\n//- Valid\nspan(class='sth:sth-else')\n```\n\n## e.g. `{ allowTokens: ['{'] }`\n```pug\n//- Invalid\nspan(class='sth::sth-else')\n\n//- Valid\nspan(class='{{::testVariable}}')\n```\n"
},
"disallowClassLiteralsBeforeAttributes": {
"enum": [
Expand Down Expand Up @@ -104,12 +119,27 @@
"documentation": "# disallowHtmlText: `true`\n\nPug must not contain any HTML text.\n\n```pug\n//- Invalid\n<strong>html text</strong>\np this is <strong>html</strong> text\n```\n"
},
"disallowIdAttributeWithStaticValue": {
"enum": [
null,
true
"anyOf": [
{
"enum": [
null,
true
]
},
{
"type": "object",
"properties": {
"allowTokens": {
"type": "array"
},
"denyTokens": {
"type": "array"
}
}
}
],
"description": "Prefer ID literals over `id` attributes with static values.",
"documentation": "# disallowIdAttributeWithStaticValue: `true`\n\nPrefer ID literals over `id` attributes with static values.\n\n```pug\n//- Invalid\nspan(id='foo')\n\n//- Valid\nspan#id\n```\n"
"documentation": "# disallowIdAttributeWithStaticValue: `true` | `object`\n\nPrefer ID literals over `id` attributes with static values.\n\n## e.g.: `true`\n```pug\n//- Invalid\nspan(id='foo')\n\n//- Valid\nspan#foo\n```\n\n## e.g. `{ denyTokens: ['::'] }`\n```pug\n//- Invalid\nspan(id='sth::sth-else')\n\n//- Valid\nspan(id='sth:sth-else')\n```\n\n## e.g. `{ allowTokens: ['{'] }`\n```pug\n//- Invalid\nspan(id='sth::sth-else')\n\n//- Valid\nspan(id='{{::testVariable}}')\n```\n"
},
"disallowIdLiteralsBeforeAttributes": {
"enum": [
Expand Down
26 changes: 26 additions & 0 deletions test/rules/disallow-class-attribute-with-static-value.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,31 @@ function createTest(linter, fixturesPath) {
assert.equal(result[0].column, 22);
});
});

describe('object', function () {
it('should not report class attribute with value containing allowed sub-string', function () {
linter.configure({disallowClassAttributeWithStaticValue: {allowTokens: ['{', '}']}});
assert.equal(linter.checkString('a(class="{{::sth}}-title")').length, 0);
});

it('should report class attribute with value containing denied sub-string', function () {
linter.configure({disallowClassAttributeWithStaticValue: {denyTokens: ['::']}});
var result = linter.checkString('a(class="{{::sth}}-title")');

assert.equal(result[0].code, 'PUG:LINT_DISALLOWCLASSATTRIBUTEWITHSTATICVALUE');
assert.equal(result[0].line, 1);
assert.equal(result[0].column, 3);
});

it('should prioritize `allowTokens` over `denyTokens`', function () {
linter.configure({
disallowClassAttributeWithStaticValue: {
allowTokens: ['::'],
denyTokens: ['::']
}
});
assert.equal(linter.checkString('a(class="{{::sth}}-title")').length, 0);
});
});
});
}
26 changes: 26 additions & 0 deletions test/rules/disallow-id-attribute-with-static-value.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,31 @@ function createTest(linter, fixturesPath) {
assert.equal(result[0].column, 19);
});
});

describe('object', function () {
it('should not report ID attribute with value containing allowed sub-string', function () {
linter.configure({disallowIdAttributeWithStaticValue: {allowTokens: ['{', '}']}});
assert.equal(linter.checkString('a(id="{{::sth}}-title")').length, 0);
});

it('should report ID attribute with value containing denied sub-string', function () {
linter.configure({disallowIdAttributeWithStaticValue: {denyTokens: ['::']}});
var result = linter.checkString('a(id="{{::sth}}-title")');

assert.equal(result[0].code, 'PUG:LINT_DISALLOWIDATTRIBUTEWITHSTATICVALUE');
assert.equal(result[0].line, 1);
assert.equal(result[0].column, 3);
});

it('should prioritize `allowTokens` over `denyTokens`', function () {
linter.configure({
disallowIdAttributeWithStaticValue: {
allowTokens: ['::'],
denyTokens: ['::']
}
});
assert.equal(linter.checkString('a(id="{{::sth}}-title")').length, 0);
});
});
});
}

0 comments on commit e7ab2f1

Please sign in to comment.