Skip to content
This repository has been archived by the owner on Apr 21, 2023. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
David Clark committed Sep 20, 2016
1 parent 39af73a commit a8cdc54
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .eslintrc
@@ -0,0 +1,3 @@
{
"extends": ["eslint-config-davidtheclark-node"]
}
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
node_modules
*.log
27 changes: 26 additions & 1 deletion README.md
@@ -1,2 +1,27 @@
# stylelint-processor-arbitrary-tags
A stylelint processor that allows you to lint CSS within arbitrary tags

A [stylelint processor](http://stylelint.io/user-guide/configuration/#processors) that allows you to lint CSS within arbitrary tags.

The module uses a regular expression to identify code within the specified tags, then passes the code on to stylelint.

By default, it looks for code within `<style>` tags.

## Options

### startTag

Type: `string` that's RegExp-ready

Default: `'\\<style[\\s\\S]*?>'`

### endTag

Type: `string` that's RegExp-ready

Default: `'</\\s*?style>'`

### body

Type: `string` that's RegExp-ready

Default: `'[\\s\\S]*?'`
61 changes: 61 additions & 0 deletions index.js
@@ -0,0 +1,61 @@
'use strict';

const execall = require('execall');
const splitLines = require('split-lines');
const reindent = require('./lib/reindent');

const sourceToLineMap = new Map();

module.exports = function (options) {
options = options || {};
options.startTag = options.startTag || '\\<style[\\s\\S]*?>';
options.endTag = options.endTag || '</\\s*?style>';
options.body = options.body || '[\\s\\S]*?';

const snippetRegexp = new RegExp(`(${options.startTag})(${options.body})${options.endTag}`, 'g');

function transformCode(code, filepath) {
const extractedToSourceLineMap = new Map();
let extractedCode = '';
let currentExtractedCodeLine = 0;

execall(snippetRegexp, code).forEach((match) => {
const openingTag = match.sub[0];
const reindentData = reindent(match.sub[1]);
const bodyText = reindentData.text;
if (!bodyText) return;

const startLine = splitLines(code.slice(0, match.index + openingTag.length)).length;
const linesWithin = splitLines(bodyText).length;

for (let i = 0; i < linesWithin; i++) {
currentExtractedCodeLine += 1;
extractedToSourceLineMap.set(currentExtractedCodeLine, {
line: startLine + i,
indentColumns: reindentData.indentColumns,
});
}

extractedCode += bodyText + '\n\n';
currentExtractedCodeLine += 1;
});

sourceToLineMap.set(filepath, extractedToSourceLineMap);
return extractedCode;
}

function transformResult(result, filepath) {
const extractedToSourceLineMap = sourceToLineMap.get(filepath);
result.warnings.forEach((warning) => {
if (!warning.line) return;
const warningSourceMap = extractedToSourceLineMap.get(warning.line);
warning.line = warningSourceMap.line;
warning.column = warning.column + warningSourceMap.indentColumns;
});
}

return {
code: transformCode,
result: transformResult,
};
};
18 changes: 18 additions & 0 deletions lib/reindent.js
@@ -0,0 +1,18 @@
// From https://github.com/sindresorhus/strip-indent,
// but providing in the result a note about the length of indentation
// that was stripped
'use strict';

module.exports = function (str) {
const match = str.match(/^[ \t]*(?=\S)/gm);

if (!match) return str;

const indent = Math.min.apply(Math, match.map((x) => x.length));
const re = new RegExp(`^[ \\t]{${indent}}`, 'gm');

return {
text: indent > 0 ? str.replace(re, '') : str,
indentColumns: indent,
};
};
38 changes: 38 additions & 0 deletions package.json
@@ -0,0 +1,38 @@
{
"name": "stylelint-processor-arbitrary-tags",
"version": "0.1.0",
"description": "A stylelint processor that allows you to lint CSS within arbitrary tags",
"main": "index.js",
"scripts": {
"lint": "eslint .",
"tap": "tap test/test.js",
"test": "npm run tap && npm run lint"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mapbox/stylelint-processor-arbitrary-tags.git"
},
"keywords": [
"stylelint",
"stylelint-processor"
],
"author": "Mapbox",
"license": "MIT",
"bugs": {
"url": "https://github.com/mapbox/stylelint-processor-arbitrary-tags/issues"
},
"homepage": "https://github.com/mapbox/stylelint-processor-arbitrary-tags#readme",
"devDependencies": {
"eslint": "^3.4.0",
"eslint-config-davidtheclark-node": "^0.1.1",
"eslint-plugin-node": "^2.0.0",
"lodash": "^4.16.0",
"stylelint": "^7.3.0",
"tap": "^7.0.0"
},
"dependencies": {
"execall": "^1.0.0",
"split-lines": "^1.1.0",
"strip-indent": "^2.0.0"
}
}
17 changes: 17 additions & 0 deletions test/fixtures/html.html
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>.foo {}</style>
</head>
<body>
<style>.foo { color: pink; }
.bar {}
</style>

<p>And some other text.</p>

<style>
.foo { color: pink; }
.bar {}</style>
</body>
</html>
20 changes: 20 additions & 0 deletions test/fixtures/liquid.md
@@ -0,0 +1,20 @@
---
title: Something Special
---

Here is some text.

{%
highlight css
%}
.foo {}
{% endhighlight %}

And some other text.

{% highlight css %}
.foo { color: pink; }
.bar {}
{% endhighlight %}

And the end.
21 changes: 21 additions & 0 deletions test/fixtures/markdown.md
@@ -0,0 +1,21 @@
---
title: Something Special
---

Here is some text.

<style
id="my-styles"
data-foo="bar"
>
.foo {}
</style>

And some other text.

<style>
.foo { color: pink; }
.bar {}
</style>

And the end.
179 changes: 179 additions & 0 deletions test/test.js
@@ -0,0 +1,179 @@
'use strict';

const test = require('tap').test;
const stylelint = require('stylelint');
const _ = require('lodash');
const path = require('path');

const pathToProcessor = path.join(__dirname, '../index.js');

const markdownExpectedWarnings = [
{
line: 11,
column: 6,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
{
line: 17,
column: 5,
rule: 'indentation',
severity: 'error',
text: 'Expected indentation of 0 spaces (indentation)',
},
{
line: 18,
column: 8,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
];

test('markdown', (t) => {
const fixture = path.join(__dirname, './fixtures/markdown.md');
stylelint.lint({
files: [fixture],
config: {
processors: [pathToProcessor],
rules: {
'block-no-empty': true,
indentation: 2,
},
},
}).then((data) => {
t.equal(data.results.length, 1, 'number of results');
const result = data.results[0];
t.equal(result.source, fixture, 'filename');
t.deepEqual(_.orderBy(result.warnings, ['line', 'column']), markdownExpectedWarnings);
t.end();
}).catch(t.threw);
});

const htmlExpectedWarnings = [
{
line: 4,
column: 6,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
{
line: 8,
column: 7,
rule: 'indentation',
severity: 'error',
text: 'Expected indentation of 0 spaces (indentation)',
},
{
line: 8,
column: 12,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
{
line: 15,
column: 5,
rule: 'indentation',
severity: 'error',
text: 'Expected indentation of 0 spaces (indentation)',
},
{
line: 15,
column: 10,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
];

test('html', (t) => {
const fixture = path.join(__dirname, './fixtures/html.html');
stylelint.lint({
files: [fixture],
config: {
processors: [pathToProcessor],
rules: {
'block-no-empty': true,
indentation: 2,
},
},
}).then((data) => {
t.equal(data.results.length, 1, 'number of results');
const result = data.results[0];
t.equal(result.source, fixture, 'filename');

t.deepEqual(_.orderBy(result.warnings, ['line', 'column']), htmlExpectedWarnings);
t.end();
}).catch(t.threw);
});

test('markdown and html', (t) => {
const fixtureOne = path.join(__dirname, './fixtures/markdown.md');
const fixtureTwo = path.join(__dirname, './fixtures/html.html');
stylelint.lint({
files: [fixtureOne, fixtureTwo],
config: {
processors: [pathToProcessor],
rules: {
'block-no-empty': true,
indentation: 2,
},
},
}).then((data) => {
t.equal(data.results.length, 2, 'number of results');

t.equal(data.results[0].source, fixtureOne);
t.deepEqual(_.orderBy(data.results[0].warnings, ['line', 'column']), markdownExpectedWarnings);

t.equal(data.results[1].source, fixtureTwo);
t.deepEqual(_.orderBy(data.results[1].warnings, ['line', 'column']), htmlExpectedWarnings);

t.end();
}).catch(t.threw);
});

const liquidExpectedWarnings = [
{
line: 10,
column: 6,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
{
line: 17,
column: 10,
rule: 'block-no-empty',
severity: 'error',
text: 'Unexpected empty block (block-no-empty)',
},
];

test('liquid, custom tags', (t) => {
const fixture = path.join(__dirname, './fixtures/liquid.md');

const options = {
startTag: '\\{%\\s*highlight css\\s*%\\}',
endTag: '\\{%\\s*endhighlight\\s*%\\}',
};

stylelint.lint({
files: [fixture],
config: {
processors: [[pathToProcessor, options]],
rules: {
'block-no-empty': true,
indentation: 2,
},
},
}).then((data) => {
t.equal(data.results.length, 1, 'number of results');
const result = data.results[0];
t.equal(result.source, fixture, 'filename');
t.deepEqual(_.orderBy(result.warnings, ['line', 'column']), liquidExpectedWarnings);
t.end();
}).catch(t.threw);
});

0 comments on commit a8cdc54

Please sign in to comment.