Skip to content

Commit

Permalink
Add named-grid-areas-no-invalid (#5167)
Browse files Browse the repository at this point in the history
  • Loading branch information
evrom committed Mar 5, 2021
1 parent dbb5bf7 commit 5d802ff
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/user-guide/rules/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Grouped first by the following categories and then by the [_thing_](http://apps.
- [`font-family-no-duplicate-names`](../../../lib/rules/font-family-no-duplicate-names/README.md): Disallow duplicate font family names.
- [`font-family-no-missing-generic-family-keyword`](../../../lib/rules/font-family-no-missing-generic-family-keyword/README.md): Disallow missing generic families in lists of font family names.

### Named grid areas

- [`named-grid-areas-no-invalid`](../../../lib/rules/named-grid-areas-no-invalid/README.md): Disallow invalid named grid areas.

### Function

- [`function-calc-no-invalid`](../../../lib/rules/function-calc-no-invalid/README.md): Disallow an invalid expression within `calc` functions.
Expand Down
1 change: 1 addition & 0 deletions lib/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ const rules = {
'media-query-list-comma-space-before': importLazy(() =>
require('./media-query-list-comma-space-before'),
)(),
'named-grid-areas-no-invalid': importLazy(() => require('./named-grid-areas-no-invalid'))(),
'no-descending-specificity': importLazy(() => require('./no-descending-specificity'))(),
'no-duplicate-at-import-rules': importLazy(() => require('./no-duplicate-at-import-rules'))(),
'no-duplicate-selectors': importLazy(() => require('./no-duplicate-selectors'))(),
Expand Down
59 changes: 59 additions & 0 deletions lib/rules/named-grid-areas-no-invalid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# named-grid-areas-no-invalid

Disallow invalid named grid areas.

<!-- prettier-ignore -->
```css
a { grid-template-areas:
"a a a"
"b b b"; }
/** ↑
* This named grid area */
```

## Options

### `true`

The following patterns are considered violations:

All strings must define the same number of cell tokens.

<!-- prettier-ignore -->
```css
a { grid-template-areas: "a a a"
"b b b b"; }
```

All strings must define at least one cell token.

<!-- prettier-ignore -->
```css
a { grid-template-areas: "" }
```

All named grid areas that spans multiple grid cells must form a single filled-in rectangle.

<!-- prettier-ignore -->
```css
a { grid-template-areas: "a a a"
"b b a"; }
```

The following patterns are _not_ considered violations:

<!-- prettier-ignore -->
```css
a { grid-template-areas: "a a a"
"b b b"; }
```

<!-- prettier-ignore -->
```css
a { grid-template-areas: "a a a" "b b b"; }
```

<!-- prettier-ignore -->
```css
a { grid-template-areas: none; }
```
138 changes: 138 additions & 0 deletions lib/rules/named-grid-areas-no-invalid/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
'use strict';

const stripIndent = require('common-tags').stripIndent;
const { messages, ruleName } = require('..');

testRule({
ruleName,

config: [true],

accept: [
{
code: 'a { grid-template-areas: "a a a" "b b b"; }',
},
{
code: 'a { GRID-TEMPLATE-AREAS: "a a a" "b b b"; }',
},
{
code: stripIndent`
a {
grid-template-areas: "head head"
"nav main"
"nav foot";
}`,
},
{
code: stripIndent`
a {
grid-template-areas:
/* "" */ "head head"
"nav main" /* "a b c" */
"nav foot" /* "" */;
}`,
},
{
code: 'a { grid-template-areas: none; }',
},
{
code: 'a { grid-template-areas: none; }',
},
{
code: 'a { grid-template-areas: /*comment*/ none /* comment */; }',
},
{
code: 'a { grid-template-areas: /*comment*/ NONE /* comment */; }',
},
{
code: 'a { grid-template-areas: NONE; }',
},
{
code: 'a { grid-template-areas: /* comment "" " */ none; }',
},
{
code: stripIndent`
a {
grid-template-areas: /* "comment" */ none /* "comment " */;
}`,
},
],

reject: [
{
code: 'a { grid-template-areas: "a a a" "b b"; }',
message: messages.expectedSameNumber(),
line: 1,
column: 26,
},
{
code: 'a { grid-template-areas: "a a a" "b b a"; }',
message: messages.expectedRectangle('a'),
line: 1,
column: 26,
},
{
code: 'a { grid-template-areas: "a c a" "c b a"; }',
warnings: [
{ message: messages.expectedRectangle('a'), line: 1, column: 26 },
{ message: messages.expectedRectangle('c'), line: 1, column: 26 },
],
},
{
code: stripIndent`
a {
grid-template-areas: "header header header header"
"main main . sidebar"
"footer footer footer header";
}`,
message: messages.expectedRectangle('header'),
line: 2,
column: 23,
},
{
code: 'a { grid-template-areas: ""; }',
message: messages.expectedToken(),
line: 1,
column: 26,
},
{
code: 'a { grid-template-areas: /* "comment" */ ""; }',
message: messages.expectedToken(),
line: 1,
column: 42,
},
{
code: stripIndent`
a { grid-template-areas:
"";
}`,
message: messages.expectedToken(),
line: 2,
column: 4,
},
{
code: 'a { grid-template-areas: "" "" ""; }',
warnings: [
{ message: messages.expectedToken(), line: 1, column: 26 },
{ message: messages.expectedToken(), line: 1, column: 29 },
{ message: messages.expectedToken(), line: 1, column: 32 },
],
},
{
code: stripIndent`
a {
grid-template-areas: /* none */
"" /* none */;
}`,
message: messages.expectedToken(),
line: 3,
column: 4,
},
{
code: 'a { GRID-TEMPLATE-AREAS: ""; }',
message: messages.expectedToken(),
line: 1,
column: 26,
},
],
});
80 changes: 80 additions & 0 deletions lib/rules/named-grid-areas-no-invalid/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// @ts-nocheck

'use strict';

const _ = require('lodash');
const declarationValueIndex = require('../../utils/declarationValueIndex');
const findNotContiguousOrRectangular = require('./utils/findNotContiguousOrRectangular');
const isRectangular = require('./utils/isRectangular');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const valueParser = require('postcss-value-parser');

const ruleName = 'named-grid-areas-no-invalid';

const messages = ruleMessages(ruleName, {
expectedToken: () => 'Expected cell token within string',
expectedSameNumber: () => 'Expected same number of cell tokens in each string',
expectedRectangle: (name) => `Expected single filled-in rectangle for "${name}"`,
});

function rule(actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });

if (!validOptions) {
return;
}

root.walkDecls(/^grid-template-areas$/i, (decl) => {
const { value } = decl;

if (value.toLowerCase().trim() === 'none') return;

const areas = [];
let reportSent = false;

valueParser(value).walk(({ sourceIndex, type, value: tokenValue }) => {
if (type !== 'string') return;

if (tokenValue === '') {
complain(messages.expectedToken(), sourceIndex);
reportSent = true;

return;
}

areas.push(_.compact(tokenValue.trim().split(' ')));
});

if (reportSent) return;

if (!isRectangular(areas)) {
complain(messages.expectedSameNumber());

return;
}

const notContiguousOrRectangular = findNotContiguousOrRectangular(areas);

notContiguousOrRectangular.sort().forEach((name) => {
complain(messages.expectedRectangle(name));
});

function complain(message, sourceIndex = 0) {
report({
message,
node: decl,
index: declarationValueIndex(decl) + sourceIndex,
result,
ruleName,
});
}
});
};
}

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

const findNotContiguousOrRectangular = require('../findNotContiguousOrRectangular');

describe('findNotContiguousOrRectangular finds', () => {
test('nothing', () => {
expect(
findNotContiguousOrRectangular([
['a', 'a', '.'],
['a', 'a', '.'],
['.', 'b', 'c'],
]),
).toEqual([]);
});

test('non-contiguous area', () => {
expect(
findNotContiguousOrRectangular([
['a', 'a', '.'],
['a', 'a', '.'],
['.', 'b', 'a'],
]),
).toEqual(['a']);
});

test('non-contiguous area when area is not in an adjacent row', () => {
expect(
findNotContiguousOrRectangular([
['header', 'header', 'header', 'header'],
['main', 'main', '.', 'sidebar'],
['footer', 'footer', 'footer', 'header'],
]),
).toEqual(['header']);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const isRectangular = require('../isRectangular');

describe('isRectangular is', () => {
test('true when rectangular', () => {
expect(
isRectangular([
['a', 'a', '.'],
['a', 'a', '.'],
['.', 'b', 'c'],
]),
).toEqual(true);
});

test('false when not-rectangular', () => {
expect(
isRectangular([
['a', 'a', '.'],
['a', 'a'],
['.', 'b', 'a'],
]),
).toEqual(false);
});
});

0 comments on commit 5d802ff

Please sign in to comment.