Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add font-family-no-duplicate-names rule #2020

Merged
merged 1 commit into from
Nov 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/user-guide/example-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ You might want to learn a little about [how rules are named and how they work to
"declaration-property-value-blacklist": {},
"declaration-property-value-whitelist": {},
"font-family-name-quotes": "always-where-required"|"always-where-recommended"|"always-unless-keyword",
"font-family-no-duplicate-names": true,
"font-weight-notation": "numeric"|"named",
"function-blacklist": string|[],
"function-calc-no-unspaced-operator": true,
Expand Down
1 change: 1 addition & 0 deletions docs/user-guide/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Here are all the rules within stylelint, grouped by the [*thing*](http://apps.wo
### Font family

- [`font-family-name-quotes`](../../src/rules/font-family-name-quotes/README.md): Specify whether or not quotation marks should be used around font family names.
- [`font-family-no-duplicate-names`](../../src/rules/font-family-no-duplicate-names/README.md): Disallow duplicate font family names.

### Font weight

Expand Down
47 changes: 47 additions & 0 deletions src/rules/font-family-no-duplicate-names/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# font-family-no-duplicate-names

Disallow duplicate font family names.

```css
a { font-family: serif, serif; }
/** ↑ ↑
* These font family names */
```

This rule checks the `font` and `font-family` properties.

This rule ignores `$sass`, `@less`, and `var(--custom-property)` variable syntaxes.

**Caveat:** This rule will stumble on *unquoted* multi-word font names and *unquoted* font names containing escape sequences. Wrap these font names in quotation marks, and everything should be fine.

## Options

### `true`

The following patterns are considered warnings:

```css
a { font-family: 'Times', Times, serif; }
```

```css
a { font: 1em "Arial", 'Arial', sans-serif; }
```

```css
a { font: normal 14px/32px -apple-system, BlinkMacSystemFont, sans-serif, sans-serif; }
```

The following patterns are *not* considered warnings:

```css
a { font-family: Times, serif; }
```

```css
a { font: 1em "Arial", "sans-serif", sans-serif; }
```

```css
a { font: normal 14px/32px -apple-system, BlinkMacSystemFont, sans-serif; }
```
58 changes: 58 additions & 0 deletions src/rules/font-family-no-duplicate-names/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
messages,
ruleName,
} from ".."
import rules from "../../../rules"
import { testRule } from "../../../testUtils"

const rule = rules[ruleName]

testRule(rule, {
ruleName,
config: [true],

accept: [ {
code: "a { font-family: \"Lucida Grande\", \"Arial\", sans-serif; }",
}, {
code: "a { font: 1em \"Lucida Grande\", \'Arial\', sans-serif; }",
}, {
code: "a { font: 1em \"Lucida Grande\", \'Arial\', \"sans-serif\", sans-serif; }",
}, {
code: "a { font-family: Times, serif; }",
}, {
code: "b { font: normal 14px/32px -apple-system, BlinkMacSystemFont, sans-serif; }",
} ],

reject: [ {
code: "a { font-family: \"Lucida Grande\", \'Arial\', sans-serif, sans-serif; }",
message: messages.rejected("sans-serif"),
line: 1,
column: 56,
}, {
code: "a { font-family: \'Arial\', \"Lucida Grande\", Arial, sans-serif; }",
message: messages.rejected("Arial"),
line: 1,
column: 44,
}, {
code: "a { fOnT-fAmIlY: \' Lucida Grande \', \"Lucida Grande\", sans-serif; }",
message: messages.rejected("Lucida Grande"),
line: 1,
column: 38,
}, {
code: "a { font-family: \'Times\', Times, \"serif\", serif; }",
message: messages.rejected("Times"),
line: 1,
column: 27,
}, {
code: "a { FONT: italic 300 16px/30px Arial, \" Arial\", serif; }",
message: messages.rejected("Arial"),
line: 1,
column: 39,
}, {
code: "b { font: normal 14px/32px -apple-system, BlinkMacSystemFont, sans-serif, sans-serif; }",
message: messages.rejected("sans-serif"),
line: 1,
column: 75,
} ],

})
66 changes: 66 additions & 0 deletions src/rules/font-family-no-duplicate-names/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
declarationValueIndex,
findFontFamily,
report,
ruleMessages,
validateOptions,
} from "../../utils"
import { fontFamilyKeywords } from "../../reference/keywordSets"

export const ruleName = "font-family-no-duplicate-names"

export const messages = ruleMessages(ruleName, {
rejected: (name) => `Unexpected duplicate name ${name}`,
})

const isFamilyNameKeyword = (node) => !node.quote && fontFamilyKeywords.has(node.value.toLowerCase())

export default function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) { return }

root.walkDecls(/^font(-family)?$/i, decl => {
const keywords = new Set()
const familyNames = new Set()

const fontFamilies = findFontFamily(decl.value)

if (fontFamilies.length === 0) { return }

fontFamilies.forEach(fontFamilyNode => {
if (isFamilyNameKeyword(fontFamilyNode)) {
const family = fontFamilyNode.value.toLowerCase()

if (keywords.has(family)) {
complain(messages.rejected(family), declarationValueIndex(decl) + fontFamilyNode.sourceIndex, decl)
return
}

keywords.add(family)
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can return early here instead of the else statement below.


const family = fontFamilyNode.value.trim()

if (familyNames.has(family)) {
complain(messages.rejected(family), declarationValueIndex(decl) + fontFamilyNode.sourceIndex, decl)
return
}

familyNames.add(family)
})
})

function complain(message, index, decl) {
report({
result,
ruleName,
message,
node: decl,
index,
})
}
}
}

2 changes: 2 additions & 0 deletions src/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import declarationPropertyUnitWhitelist from "./declaration-property-unit-whitel
import declarationPropertyValueBlacklist from "./declaration-property-value-blacklist"
import declarationPropertyValueWhitelist from "./declaration-property-value-whitelist"
import fontFamilyNameQuotes from "./font-family-name-quotes"
import fontFamilyNoDuplicateNames from "./font-family-no-duplicate-names"
import fontWeightNotation from "./font-weight-notation"
import functionBlacklist from "./function-blacklist"
import functionCalcNoUnspacedOperator from "./function-calc-no-unspaced-operator"
Expand Down Expand Up @@ -227,6 +228,7 @@ export default {
"declaration-property-value-blacklist": declarationPropertyValueBlacklist,
"declaration-property-value-whitelist": declarationPropertyValueWhitelist,
"font-family-name-quotes": fontFamilyNameQuotes,
"font-family-no-duplicate-names": fontFamilyNoDuplicateNames,
"font-weight-notation": fontWeightNotation,
"function-blacklist": functionBlacklist,
"function-calc-no-unspaced-operator": functionCalcNoUnspacedOperator,
Expand Down