├── test
├── fixture
│ ├── .csshintignore
│ ├── has-bom.css
│ ├── block-indent5.css
│ ├── csshintignore.css
│ ├── require-around-space.css
│ ├── require-number.css
│ ├── unicode.css
│ ├── disallow-important.css
│ ├── horizontal-vertical-position.css
│ ├── star-property-hack.css
│ ├── underscore-property-hack.css
│ ├── import.css
│ ├── adjoining-classes.css
│ ├── unifying-font-family-case-sensitive1.css
│ ├── require-before-space.css
│ ├── block-indent8.css
│ ├── disallow-named-color.css
│ ├── block-indent-new1.css
│ ├── box-sizing.css
│ ├── no-bom.css
│ ├── block-indent7.css
│ ├── require-transition-property.css
│ ├── block-indent.css
│ ├── property-not-existed.css
│ ├── unifying-font-family-case-sensitive.css
│ ├── max-length.css
│ ├── qualified-headings.css
│ ├── unqualified-attributes.css
│ ├── empty-rules.css
│ ├── ids.css
│ ├── universal-selector.css
│ ├── duplicate-background-images.css
│ ├── outline-none.css
│ ├── zero-unit.css
│ ├── require-after-space.css
│ ├── disallow-expression.css
│ ├── min-font-size.css
│ ├── cr1.css
│ ├── unifying-color-case-sensitive.css
│ ├── unique-headings.css
│ ├── max-selector-nesting-level.css
│ ├── text-indent.css
│ ├── disallow-quotes-in-url.css
│ ├── always-semicolon.css
│ ├── error-char.css
│ ├── always-semicolon2.css
│ ├── leading-zero.css
│ ├── block-indent-new2.css
│ ├── display-property-grouping.css
│ ├── hex-color.css
│ ├── omit-protocol-in-url.css
│ ├── regex-selectors.css
│ ├── .csshintrc
│ ├── .csshintrc.yml
│ ├── block-indent2.css
│ ├── max-length2.css
│ ├── disallow-overqualified-elements1.css
│ ├── duplicate-properties.css
│ ├── block-indent6.css
│ ├── box-model.css
│ ├── require-after-space2.css
│ ├── gradients.css
│ ├── require-doublequotes.css
│ ├── bulletproof-font-face.css
│ ├── fallback-colors.css
│ ├── require-newline.css
│ ├── test.css
│ ├── shorthand.css
│ ├── floats.css
│ ├── block-indent4.css
│ ├── font-sizes.css
│ ├── block-indent1.css
│ ├── inline-comment.css
│ ├── vendor-prefixes-sort.css
│ ├── disallow-overqualified-elements.css
│ ├── reset.css
│ ├── cr.css
│ ├── font-face.css
│ ├── index.css
│ ├── rss.css
│ └── block-indent3.css
└── spec
│ ├── prefixes.spec.js
│ └── checker.spec.js
├── .babelrc
├── bin
└── csshint-cli
├── .travis.yml
├── .gitignore
├── .npmignore
├── .fecsrc
├── .eslintrc
├── .jshintrc
├── src
├── rule
│ ├── no-bom.js
│ ├── font-face.js
│ ├── font-sizes.js
│ ├── floats.js
│ ├── import.js
│ ├── max-length.js
│ ├── star-property-hack.js
│ ├── underscore-property-hack.js
│ ├── require-transition-property.js
│ ├── box-sizing.js
│ ├── empty-rules.js
│ ├── duplicate-properties.js
│ ├── horizontal-vertical-position.js
│ ├── disallow-quotes-in-url.js
│ ├── disallow-expression.js
│ ├── require-before-space.js
│ ├── disallow-important.js
│ ├── always-semicolon.js
│ ├── adjoining-classes.js
│ ├── property-not-existed.js
│ ├── disallow-named-color.js
│ ├── hex-color.js
│ ├── min-font-size.js
│ ├── ids.js
│ ├── require-number.js
│ ├── text-indent.js
│ ├── disallow-overqualified-elements.js
│ ├── omit-protocol-in-url.js
│ ├── qualified-headings.js
│ ├── unqualified-attributes.js
│ ├── universal-selector.js
│ ├── regex-selectors.js
│ ├── leading-zero.js
│ ├── max-selector-nesting-level.js
│ ├── zero-unit.js
│ ├── bulletproof-font-face.js
│ ├── gradients.js
│ ├── duplicate-background-images.js
│ ├── unique-headings.js
│ ├── unifying-font-family-case-sensitive.js
│ ├── outline-none.js
│ ├── fallback-colors.js
│ ├── require-around-space.js
│ ├── display-property-grouping.js
│ └── require-after-space.js
├── config.js
├── cli.js
├── prefixes.js
└── colors.js
├── package.json
├── README.zh-CN.md
├── README.md
└── CHANGELOG.md
/test/fixture/.csshintignore:
--------------------------------------------------------------------------------
1 | csshintignore.css
2 |
--------------------------------------------------------------------------------
/test/fixture/has-bom.css:
--------------------------------------------------------------------------------
1 | a {
2 | color: #f00;
3 | }
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-2"]
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/block-indent5.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/csshintignore.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/require-around-space.css:
--------------------------------------------------------------------------------
1 | div ~ span >p {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/require-number.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-weight: bold;
3 | line-height: 50px;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/unicode.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ecomfe/node-csshint/HEAD/test/fixture/unicode.css
--------------------------------------------------------------------------------
/test/fixture/disallow-important.css:
--------------------------------------------------------------------------------
1 | body {
2 | color:red !important;height: 100px !important;
3 | }
4 |
--------------------------------------------------------------------------------
/bin/csshint-cli:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | var cli = require('../lib/cli');
4 | cli.parse(process.argv);
5 |
--------------------------------------------------------------------------------
/test/fixture/horizontal-vertical-position.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-position: center; /* 50% 0% */
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture/star-property-hack.css:
--------------------------------------------------------------------------------
1 | .mybox {
2 | border: 1px solid black;
3 | *width: 100px;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/underscore-property-hack.css:
--------------------------------------------------------------------------------
1 | .mybox {
2 | border: 1px solid black;
3 | _width: 100px;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/import.css:
--------------------------------------------------------------------------------
1 | @import url(more.css);
2 | @import url(andmore.css);
3 |
4 | a {
5 | color: black;
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixture/adjoining-classes.css:
--------------------------------------------------------------------------------
1 | .cc .foo.bar {
2 | color: #f00;
3 | }
4 |
5 | .foo.bar {
6 | color: #f00;
7 | }
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | - "4"
5 | - "6"
6 | after_script:
7 | - npm run coveralls
8 |
--------------------------------------------------------------------------------
/test/fixture/unifying-font-family-case-sensitive1.css:
--------------------------------------------------------------------------------
1 |
2 | h1 {
3 | font-family: Arial, "Microsoft YaHei", sans-serif;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/require-before-space.css:
--------------------------------------------------------------------------------
1 |
2 | .hetu_top_nav_body{
3 | display: none;
4 | }
5 |
6 | asdasd {
7 | color: red;
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | npm-debug.log
4 | Thumbs.db
5 | .DS_Store
6 | *.swp
7 | *bak*
8 | lib
9 | coverage
10 | output
11 |
--------------------------------------------------------------------------------
/test/fixture/block-indent8.css:
--------------------------------------------------------------------------------
1 | /* csshint block-indent: [" ", 4] */
2 |
3 | body {
4 | margin: 0px;
5 | padding: 0
6 | }
--------------------------------------------------------------------------------
/test/fixture/disallow-named-color.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red ;
3 | color: #f00;
4 | width: 100px;
5 | border: 1px solid black;
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixture/block-indent-new1.css:
--------------------------------------------------------------------------------
1 | /* csshint block-indent: [" ", 3]*/
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixture/box-sizing.css:
--------------------------------------------------------------------------------
1 | .box {
2 | -webkit-box-sizing: border-box;
3 | -moz-box-sizing: border-box;
4 | box-sizing: border-box;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixture/no-bom.css:
--------------------------------------------------------------------------------
1 |
2 | #ccc .fff .qqq, #ccc .fff a.cwsirrss_entry-source-title, #ccc .fff a.cwsirrss_entry-post-author-name {
3 | color: #15c;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/block-indent7.css:
--------------------------------------------------------------------------------
1 | /**
2 | * 注意 div 的 } 符号后有空格
3 | */
4 |
5 | div {
6 | margin-top: -4px;
7 | }
8 | a {
9 | border: 0 solid #fff;
10 | }
--------------------------------------------------------------------------------
/test/fixture/require-transition-property.css:
--------------------------------------------------------------------------------
1 | .box {
2 | transition: all 1s;
3 | transition: width 2s;
4 | transition: color 1s, sdall 10s, all 20s;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixture/block-indent.css:
--------------------------------------------------------------------------------
1 | /* csshint-disable no-bom */ /* csshint block-indent: [" ", 0]*/
2 | body{
3 | height: 10;
4 | width: 2px;
5 | aa:12px;width: 12px;
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixture/property-not-existed.css:
--------------------------------------------------------------------------------
1 | .aa {
2 | -webkit-border-radius: 5px;
3 | -moz-border-radius: 5px;
4 | -o-border-radius: 5px;
5 | border-radius: 5px;
6 | }
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | npm-debug.log
4 | Thumbs.db
5 | .DS_Store
6 | *.swp
7 | *.bak
8 | src
9 | .babelignore
10 | .gitignore
11 | .npmignore
12 | coverage
13 | output
14 | test
15 |
--------------------------------------------------------------------------------
/test/fixture/unifying-font-family-case-sensitive.css:
--------------------------------------------------------------------------------
1 |
2 | h1 {
3 | font-family: arial, "Microsoft YaHei", sans-serif;
4 | }
5 |
6 | body {
7 | font-family: Arial, "Microsoft YaHei", sans-serif;
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixture/max-length.css:
--------------------------------------------------------------------------------
1 |
2 | #ccc .fff .qqq, #ccc .fff .qqq a, .aaaa, #ccc .fff a.cwsirrss_entry-source-title, #ccc .fff a.cwsirrss_entry-post-author-namecwsirrss_entry-post-author-name {
3 | color: #15c;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture/qualified-headings.css:
--------------------------------------------------------------------------------
1 | /* qualified heading */
2 | .box h3 {
3 | font-weight: normal;
4 | }
5 |
6 | /* qualified heading */
7 | .item:hover ,
8 | .aaa h3 {
9 | font-weight: bold;
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixture/unqualified-attributes.css:
--------------------------------------------------------------------------------
1 | [type=text] {
2 | color: red;
3 | }
4 |
5 | .selected [type=text] {
6 | color: red;
7 | }
8 |
9 | .selected ,.selectedasdsa [type=text] {
10 | color: red;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixture/empty-rules.css:
--------------------------------------------------------------------------------
1 | .mybox { }
2 |
3 | .mybox {
4 |
5 | }
6 |
7 | .mybox {
8 | /* a comment */
9 | }
10 | @media handheld and (min-width:360px), screen and (min-width:480px) {
11 | body {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixture/ids.css:
--------------------------------------------------------------------------------
1 | #mybox {
2 | display: block;
3 | }
4 |
5 | .mybox #go {
6 | color: red;
7 | }
8 |
9 | .mybox #go a{
10 | color: red;
11 | }
12 |
13 | .aaa,
14 | .cc #go a b {
15 | color: red;
16 | }
17 |
--------------------------------------------------------------------------------
/test/fixture/universal-selector.css:
--------------------------------------------------------------------------------
1 | * {
2 | color: red;
3 | }
4 |
5 | .selected * {
6 | color: red;
7 | }
8 |
9 | .aaa .bbb > *,
10 | #ccc * {
11 | color: red;
12 | }
13 |
14 | #asdsadasd * {
15 | color: red;
16 | }
17 |
--------------------------------------------------------------------------------
/test/fixture/duplicate-background-images.css:
--------------------------------------------------------------------------------
1 | /* multiple instances of the same URL */
2 | .heart-icon {
3 | background: url(sprite.png) -16px 0 no-repeat;
4 | }
5 |
6 | .task-icon {
7 | background: url(sprite.png) -32px 0 no-repeat;
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixture/outline-none.css:
--------------------------------------------------------------------------------
1 | /* no :focus */
2 | a {
3 | outline: none;
4 | }
5 |
6 | /* no :focus */
7 | a {
8 | outline: 0;
9 | }
10 |
11 | /* :focus but missing a replacement treatment */
12 | a:focus {
13 | outline: 0;
14 | }
15 |
--------------------------------------------------------------------------------
/test/fixture/zero-unit.css:
--------------------------------------------------------------------------------
1 | div {
2 | width: 0px ;
3 | background-position: 0px 0 1.2em 0px;
4 | height: 0em;
5 | aaa: 0s;
6 | qqq: 0%;
7 | bbb: 0ms;
8 | ccc: 0deg;
9 | ddd: 0%;
10 | padding: 0px 0px;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixture/require-after-space.css:
--------------------------------------------------------------------------------
1 | span {
2 | *border-color:rgb(200,200,200);color: rgb(100,100,100);
3 | }
4 |
5 | div {
6 | background-image: 2px 2px url(data:image/gif;base64,R0lGODlhCAAHAIABAGZmZv///yH5BAEAAAEALAAAAAAIAAcAAAINjAOnkIr8mGxGOnhPAQA7);
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixture/disallow-expression.css:
--------------------------------------------------------------------------------
1 | input {
2 | width: expression(onmouseover=this.style.backgroundColor="#F5F5F5";
3 | onmouseout=this.style.backgroundColor="#FFFFFF");
4 | }
5 |
6 |
7 | input {
8 | width: expression(this.offsetWidth > 750 ? '12px' : '20px');
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixture/min-font-size.css:
--------------------------------------------------------------------------------
1 | span {
2 | font-size: 11px;font-size: 11px;
3 | }
4 |
5 | div {
6 | font-size: 13px;
7 | }
8 |
9 |
10 | a {
11 | font-size:1px;
12 | }
13 |
14 | .dd {
15 | width: 5px;
16 | }
17 |
18 | .c {
19 | font-size: 1.2em;
20 | }
21 |
--------------------------------------------------------------------------------
/test/fixture/cr1.css:
--------------------------------------------------------------------------------
1 | /*body{}
2 | .aaa {
3 | line-height: .7;
4 | }
5 | */
6 | .bbb {
7 | font-family: "Microsoft Yahei", Verdana, Simsun, "Segoe UI Web Light", "Segoe UI Light",
8 | "Segoe UI Web Regular", "Segoe UI", "Segoe UI Symbol", "Helvetica Neue", Arial;
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixture/unifying-color-case-sensitive.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: #fff;
3 | color: #FFF;
4 | color: #000abC;
5 | color: #eeeeee;
6 | color: #CCC;
7 | border: 1px solid rgb(255, 255, 255);
8 | background: url("images/icons.png") #acacac no-repeat;
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/test/fixture/unique-headings.css:
--------------------------------------------------------------------------------
1 | /* Two rules for h3 */
2 | h3 {
3 | font-weight: normal;
4 | }
5 |
6 | .box h3 {
7 | font-weight: bold;
8 | }
9 |
10 | .cc h3:hover,
11 |
12 | .dd h3 {
13 | color: red;
14 | }
15 |
16 | .cc .cc {
17 | height: 100px;
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixture/max-selector-nesting-level.css:
--------------------------------------------------------------------------------
1 | body div span {
2 | color: red;
3 | }
4 |
5 | body > div > span > a,
6 | .aa .bb .cc .dd {
7 | color: green;
8 | }
9 |
10 |
11 | .page .header .login ~ .dsa {
12 | height: 100px;
13 | }
14 |
15 | p span {
16 | color: red;
17 | }
18 |
--------------------------------------------------------------------------------
/test/fixture/text-indent.css:
--------------------------------------------------------------------------------
1 | /* missing direction */
2 | .mybox {
3 | text-indent: -999px;
4 | }
5 |
6 | /* missing direction */
7 | .mybox {
8 | text-indent: -999em;
9 | }
10 |
11 | /* direction is rtl */
12 | .mybox {
13 | direction: rtl;
14 | text-indent: -999em;
15 | }
16 |
--------------------------------------------------------------------------------
/.fecsrc:
--------------------------------------------------------------------------------
1 | files:
2 | - src
3 | eslint:
4 | env:
5 | es6: true
6 | rules:
7 | no-console: 0
8 | fecs-camelcase:
9 | - 2
10 | -
11 | ignore:
12 | - "/-_/"
13 | fecs-min-vars-per-destructure: false
14 |
--------------------------------------------------------------------------------
/test/fixture/disallow-quotes-in-url.css:
--------------------------------------------------------------------------------
1 | span {
2 | background:#fff url("http://cwsir.sinaapp.com/banner.jpg") no-repeat center 0;
3 | background:#000 url('http://baidu.com/1.jpg') no-repeat url("http://asdasd.com/sda.png") 0;
4 | /*background:#aaa url(http://qq.com/2.jpg) no-repeat center 0;*/
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | },
5 | "rules": {
6 | "no-console": 0
7 | },
8 | "parserOptions": {
9 | "ecmaVersion": 7,
10 | "sourceType": "module",
11 | "ecmaFeatures": {
12 | "jsx": true
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixture/always-semicolon.css:
--------------------------------------------------------------------------------
1 | div {
2 | margin: 0
3 | }
4 |
5 | span {
6 | padding: 0
7 | }
8 |
9 | p {
10 | max-width: 500px !important
11 | }
12 | .function {padding-bottom:20px;border-bottom:1px solid #e3e3e3}
13 |
14 |
15 | .cc {
16 | display: inline-block;
17 | *display: inline
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/test/fixture/error-char.css:
--------------------------------------------------------------------------------
1 | .wm-location-show-middle span {
2 | display: inline-block;
3 | margin-left: 4px;
4 | width: 11px;
5 | height: 11px;
6 | background:url(//m.baidu.com/static/search/appAla/weizhan/icon_how.png) no-repeat;
7 | background-size: 11px 11px;
8 | vertical-align: -1px;
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixture/always-semicolon2.css:
--------------------------------------------------------------------------------
1 | div {
2 | margin: 0
3 | }
4 |
5 | span {
6 | padding: 0
7 | }
8 |
9 | p {
10 | max-width: 500px !important
11 | }
12 | .function {padding-bottom:20px;border-bottom:1px solid #e3e3e3}
13 |
14 |
15 | .cc {
16 | display: inline-block;
17 | *display: inline
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/test/fixture/leading-zero.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 0.5px;
3 | height: 10.10px;
4 | font-size: 12.5px;
5 | margin: 0.5px 1.2px 0.8px;
6 | color: #f00;
7 | text-transform: 13.0ms;
8 | }
9 |
10 | .for-test {
11 | color: rgba(0, 0, 0, 0.25);
12 | opacity: 0.6;
13 | transform: scale(0.5);
14 | }
15 |
--------------------------------------------------------------------------------
/test/fixture/block-indent-new2.css:
--------------------------------------------------------------------------------
1 | .closure.modal > .content > .image {
2 | display: block;
3 | width: "";
4 | -webkit-box-flex: 0;
5 | -webkit-flex: 0 1 auto;
6 | -ms-flex: 0 1 auto;
7 | flex: 0 1 auto;
8 | -webkit-align-self: top;
9 | align-self: top;
10 | -ms-flex-item-align: top;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixture/display-property-grouping.css:
--------------------------------------------------------------------------------
1 | /* inline with height */
2 | .mybox {
3 | display: inline;
4 | height: 25px;
5 | }
6 |
7 | /* inline-block with float */
8 | .mybox {
9 | display: inline-block;
10 | float: left;
11 | }
12 |
13 | /* table-cell and margin */
14 | .mybox {
15 | display: table-cell;
16 | margin: 10px;
17 | }
18 |
--------------------------------------------------------------------------------
/test/fixture/hex-color.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: rgb(255, 255, 255);
3 | color: rgb(20%, 20%, 20%);
4 | color: rgba(255, 255, 255, 1);
5 | color: hsla(120, 65%, 75%, 1);
6 | color: #fff;
7 | border: 0px solid rgb(255, 255, 255);
8 | /*border: 0px solid hsl(120, 65%, 75%);*/
9 | /*color: rgb(11%, 11%, 11%) rgb(55%, 55%, 55%);*/
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixture/omit-protocol-in-url.css:
--------------------------------------------------------------------------------
1 | span {
2 | background:#fff url("http://cwsir.sinaapp.com/banner.jpg") no-repeat center 0;
3 | background:#000 url('http://baidu.com/1.jpg') no-repeat url("http://asdasd.com/sda.png") 0;
4 | background:#aaa url(http://qq.com/2ee.jpg) no-repeat center 0;
5 | background:#aaa url(qq.com/2.jpg) no-repeat center 0;
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixture/regex-selectors.css:
--------------------------------------------------------------------------------
1 | #mybox[class~="xxx"] .ccc[asd~="yyy"],
2 | #ll[class~="gg"] .bb[nn~="ii"] {
3 | color: red;
4 | }
5 |
6 | .mybox[class^=xxx] {
7 | color: red;
8 | }
9 |
10 | .mybox[class|=xxx] {
11 | color: red;
12 | }
13 |
14 | .mybox[class$=xxx] {
15 | color: red;
16 | }
17 |
18 | .mybox[class*=xxx] {
19 | color: red;
20 | }
21 |
--------------------------------------------------------------------------------
/test/fixture/.csshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "max-error": 1000,
3 | "no-bom": true,
4 | "require-before-space": ["{"],
5 | "require-after-space": [":", ","],
6 | "max-length": 120,
7 | "disallow-important": true,
8 | "require-around-space": [ "+", "~", ">"],
9 | "max-selector-nesting-level": 3,
10 | "require-newline": ["selector", "property", "media-query-condition"]
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixture/.csshintrc.yml:
--------------------------------------------------------------------------------
1 | max-error: 1000
2 | no-bom: true
3 | require-before-space: ['{']
4 | require-after-space:
5 | - ':'
6 | - ','
7 | max-length: 120
8 | disallow-important: true
9 | require-around-space:
10 | - '+'
11 | - '~'
12 | - '>'
13 | max-selector-nesting-level: 3
14 | require-newline:
15 | - 'selector'
16 | - 'property'
17 | - 'media-query-condition'
18 |
--------------------------------------------------------------------------------
/test/fixture/block-indent2.css:
--------------------------------------------------------------------------------
1 | a {
2 | color: #fff;
3 | }
4 | @media handheld and (min-width:360px), screen and (min-width:480px) {
5 | body {
6 | font-size:large;
7 | }
8 | }
9 |
10 | @keyframes errorbg {
11 | 0%,
12 | 25% {
13 | background: #f5cfc4;
14 | opacity: .18;
15 | }
16 | 100% {
17 | background: #f5cfc4;
18 | opacity: .18;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/fixture/max-length2.css:
--------------------------------------------------------------------------------
1 | #ccc .fff .qqq, #ccc .fff .qqq a, .aaaa, #ccc .fff a.cwsirrss_entry-source-title, #ccc .fff a.cwsirrss_entry-post-author-name {
2 | color: #15c;color: #151;color: #152;color: #153;color: #154;color: #155;color: #156;color: #157;color: #158;color: #159;color: #160;
3 | }
4 |
5 | div {
6 | background-image: 2px 2px url(data:image/gif;base64,R0lGODlhCAAHAIABAGZmZv///yH5BAEAAAEALAAAAAAIAAcAAAINjAOnkIr8mGxGOnhPAQA7);
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixture/disallow-overqualified-elements1.css:
--------------------------------------------------------------------------------
1 | @keyframes btnLoader {
2 | 87.5% {
3 | box-shadow: 0 -3em 0 0 #fff,
4 | 2em -2em 0 -0.5em #fff,
5 | 3em 0 0 -0.5em #fff,
6 | 2em 2em 0 -0.5em #fff,
7 | 0 3em 0 -0.5em #fff,
8 | -2em 2em 0 0 #fff,
9 | -3em 0 0 0 #fff,
10 | -2em -2em 0 .2em #fff;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixture/duplicate-properties.css:
--------------------------------------------------------------------------------
1 | /* properties with the same value */
2 | .mybox {
3 | border: 1px solid black;
4 | border: 1px solid black;
5 | }
6 |
7 | /* properties separated by another property */
8 | .mybox {
9 | border: 1px solid black;
10 | color: green;
11 | border: 1px solid red;
12 | }
13 |
14 |
15 | /* one after another with different values */
16 | .mybox {
17 | border: 1px solid black;
18 | border: 1px solid red;
19 | }
20 |
--------------------------------------------------------------------------------
/test/fixture/block-indent6.css:
--------------------------------------------------------------------------------
1 |
2 | .branch-box {
3 | border-radius: 10px;
4 | background: #f5f5f5 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAb0lEQVR42p2RMQ7AIAhFWXsG1h7Gtffo6urqyJGpwyetomnwJ38BH/wgfZSaS7PABbVOqkqm3Fzx6IQTatlDb5PJi21YBy1iuNgjJIgzFXqyCcXj1Z1DXNGTiw01aP252DCCgH7Fk41h8KaAGMDxADnaOPucd/m3AAAAAElFTkSuQmCC) no-repeat 7px 6px;;
5 | background-position-x: 170px;
6 | background-position-y: 5px;
7 | padding-left: 10px;
8 | padding-right: 40px;
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixture/box-model.css:
--------------------------------------------------------------------------------
1 | /* width and border with box-sizing */
2 | .mybox {
3 | box-sizing: border-box;
4 | border: 1px solid black;
5 | width: 100px;
6 | }
7 |
8 | /* width and border-top */
9 | .mybox {
10 | border-top: 1px solid black;
11 | width: 100px;
12 | }
13 |
14 | /* height and border-top of none */
15 | .mybox {
16 | border-top: none;
17 | height: 100px;
18 | }
19 |
20 | /* width and border */
21 | .mybox {
22 | border: 1px solid black;
23 | width: 100px;
24 | }
25 |
--------------------------------------------------------------------------------
/test/fixture/require-after-space2.css:
--------------------------------------------------------------------------------
1 | /*.aaa .ddd::-webkit-scrollbar-thumb {
2 | background: rgba(0, 0, 0,0.6);
3 | }
4 | */
5 |
6 | span {
7 | font-family: Arial,sans-serif,cas;
8 | color :red;
9 | color:yellow;
10 | color: green;
11 | }
12 |
13 | /*
14 | body,A {
15 | color : #CcccCd;font-size:0px;
16 | border-color:rgb(200,200,200); color:rgb(100, 100,100);}
17 | */
18 |
19 | /*div {
20 | background-image: url(data:image/gif;base64,R0lGODlhCAAHAIABAGZmZv///yH5BAEAAAEALAAAAAAIAAcAAAINjAOnkIr8mGxGOnhPAQA7);
21 | }*/
22 |
--------------------------------------------------------------------------------
/test/fixture/gradients.css:
--------------------------------------------------------------------------------
1 | /* Missing -moz, -ms, and -o */
2 | .mybox {
3 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #1e5799), color-stop(100%, #7db9e8));
4 | background: -webkit-linear-gradient(top, #1e5799 0%, #7db9e8 100%);
5 | }
6 |
7 | /* Missing old and new -webkit */
8 | .mybox {
9 | background: -moz-linear-gradient(top, #1e5799 0%, #7db9e8 100%);
10 | background: -o-linear-gradient(top, #1e5799 0%, #7db9e8 100%);
11 | background: -ms-linear-gradient(top, #1e5799 0%, #7db9e8 100%);
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixture/require-doublequotes.css:
--------------------------------------------------------------------------------
1 | /*html[lang|="zh"] q:before {
2 | font-family: "Microsoft YaHei", sans-serif;
3 | content: "“";
4 | }
5 | */
6 | /*html[lang|=zh] q:before {
7 | font-family: 'Microsoft YaHei', sans-serif;
8 | content: '“';
9 | }*/
10 |
11 | html[lang|='zh'] q:before {
12 | font-family: 'Microsoft YaHei', sans-serif;
13 | content: '';
14 | background: #fff url("http://cwsir.sinaapp.com/banner.jpg") no-repeat center 0;
15 | }
16 |
17 | input[type=text] {
18 | width: 100px;
19 | content: "asdasd";
20 | }
21 |
22 | .Table th[data-sort]:hover {
23 | cursor: pointer;
24 | }
25 |
--------------------------------------------------------------------------------
/test/fixture/bulletproof-font-face.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'MyFontFamily';
3 | src: url('myfont-webfont.eot?#iefix') format('embedded-opentype'),
4 | url('myfont-webfont.woff') format('woff'),
5 | url('myfont-webfont.ttf') format('truetype'),
6 | url('myfont-webfont.svg#svgFontName') format('svg');
7 | }
8 |
9 | @font-face {
10 | font-family: 'HarlowSolid';
11 | src: url('harlowsi-webfont.eot?') format('eot'),
12 | url('harlowsi-webfont.woff') format('woff'),
13 | url('harlowsi-webfont.ttf') format('truetype'),
14 | url('harlowsi-webfont.svg#harlowsi-webfont') format('svg');
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixture/fallback-colors.css:
--------------------------------------------------------------------------------
1 | /* missing fallback color */
2 | .mybox {
3 | color: rgba(100, 200, 100, 0.5);
4 | }
5 |
6 | /* missing fallback color */
7 | .mybox {
8 | background-color: green;
9 | background-color: hsla(100, 50%, 100%, 0.5);
10 | }
11 |
12 | /* missing fallback color */
13 | .mybox {
14 | background: hsla(100, 50%, 100%, 0.5) url(foo.png);
15 | }
16 |
17 | /* fallback color should be before */
18 | .mybox {
19 | background-color: hsl(100, 50%, 100%);
20 | background-color: green;
21 | }
22 |
23 | /* fallback color before newer format */
24 | .mybox {
25 | color: red;
26 | color: rgba(255, 0, 0, 0.5);
27 | }
28 |
--------------------------------------------------------------------------------
/test/fixture/require-newline.css:
--------------------------------------------------------------------------------
1 |
2 | @media handheld and (min-width:360px),
3 | screen and (min-width:480px),screen and (max-width:980px) {
4 | body {font-size:large;}
5 | }
6 |
7 | @media screen and (min-width:1024px) and (max-width:1280px) {
8 | body {font-size:medium;}
9 | }
10 |
11 | @media screen and (min-width:800px),print and (min-width:7in) {
12 | body {font-size:small;}
13 | }
14 |
15 | span, label {
16 | color: #f00;
17 | }
18 |
19 |
20 | div,
21 | a:hover {
22 | color: #ccc;
23 | }
24 |
25 |
26 | span,
27 | label {
28 | color: #f00;
29 | }
30 |
31 | p {
32 | width: 10px;height: 30px;
33 | }
34 |
35 |
36 | p,
37 | i,.cc {
38 | width: 100px;height: 100px;
39 | }
40 |
--------------------------------------------------------------------------------
/test/fixture/test.css:
--------------------------------------------------------------------------------
1 | /*.ani {
2 | -webkit-animation: spin 2s infinite linear;
3 | -moz-animation: spin 2s infinite linear;
4 | -ms-animation: spin 2s infinite linear;
5 | -o-animation: spin 2s infinite linear;
6 | animation: spin 2s infinite linear;
7 | color: #fff;
8 | }
9 | .cc {
10 | -webkit-filter: blur(11px);
11 | filter: blur(11px);
12 | }
13 | */
14 |
15 |
16 | .wm-location-show-middle span {
17 | display: inline-block;
18 | margin-left: 4px;
19 | width: 11px;
20 | height: 11px;
21 | background:url(//m.baidu.com/static/search/appAla/weizhan/icon_how.png) no-repeat;
22 | background-size: 11px 11px;
23 | vertical-align: -1px;
24 | }
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "camelcase": true,
4 | "devel": true,
5 | "expr": true,
6 | "curly": true,
7 | "eqeqeq": true,
8 | "forin": true,
9 | "immed": true,
10 | "latedef": "nofunc",
11 | "newcap": true,
12 | "noarg": true,
13 | "nonew": true,
14 | "quotmark": "single",
15 | "sub": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": false,
19 | "globalstrict": true,
20 | "trailing": true,
21 | "browser": true,
22 | "maxparams": 5,
23 | "maxdepth": 5,
24 | "maxstatements": 25,
25 | "maxcomplexity": 10,
26 | "laxbreak": true,
27 | "node": true,
28 | "predef": [
29 | "System"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/test/fixture/shorthand.css:
--------------------------------------------------------------------------------
1 | #review-head {
2 | /*color: #a37e31;
3 | line-height: 60px;
4 | font-size: 1.5em;
5 | font-family: "Microsoft YaHei";
6 | margin-top: 50px;
7 | margin-left: 85px;*/
8 | /*color: #aaccaa;*/
9 | /*border: 1px solid #ffffff #ccddcc;*/
10 | line-height: 60px;
11 | font-size: 1.5em;
12 | font-family: "Microsoft YaHei";
13 | }
14 |
15 | .aaa {
16 | display: block;
17 | padding: 0 15px 0 0px;
18 | width: 175px;
19 | font-family: "Microsoft YaHei";
20 | }
21 |
22 | .bbb {
23 | display: inline;
24 | margin-bottom: 3px;
25 | font-size: 16px;
26 | line-height: 60;
27 | margin-top: 10px;
28 | margin-bottom: 10px;
29 | margin-left: 10px;
30 | margin-right: 10px;
31 | }
32 |
--------------------------------------------------------------------------------
/test/spec/prefixes.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file lib/prefixes.js的测试用例
3 | * @author ielgnaw(wuji0223@gmail.com)
4 | */
5 |
6 | import chai from 'chai';
7 | import path from 'path';
8 |
9 | 'use strict';
10 |
11 | let prefixes = require(path.join(__dirname, '../../lib', 'prefixes'));
12 |
13 | const expect = chai.expect;
14 |
15 | /* globals describe, it */
16 |
17 | describe('prefixes test suite\n', () => {
18 | describe('prefixes', function () {
19 | it('should be a array', function () {
20 | expect(Object.prototype.toString.call(prefixes.getPrefixList())).to.equal('[object Array]');
21 | });
22 |
23 | it('should be a object', function () {
24 | expect(typeof prefixes.getPrefixMap()).to.equal('object');
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/fixture/floats.css:
--------------------------------------------------------------------------------
1 | .user-info-list {
2 | height: 90px;
3 | float: left;
4 | }
5 |
6 | .aa {
7 | height: 90px;
8 | float: left;
9 | }
10 |
11 | .bb {
12 | height: 90px;
13 | float: left;
14 | }
15 |
16 | .cc {
17 | height: 90px;
18 | float: left;
19 | }
20 |
21 | .dd {
22 | height: 90px;
23 | float: left;
24 | }
25 |
26 | .ee {
27 | height: 90px;
28 | float: left;
29 | }
30 |
31 | .ff {
32 | height: 90px;
33 | float: left;
34 | }
35 |
36 | .gg {
37 | height: 90px;
38 | float: left;
39 | }
40 |
41 | .hh {
42 | height: 90px;
43 | float: left;
44 | }
45 |
46 | .ii {
47 | height: 90px;
48 | float: left;
49 | }
50 |
51 | .jj {
52 | height: 90px;
53 | float: left;
54 | }
55 |
56 | .kk {
57 | height: 90px;
58 | float: left;
59 | }
60 |
--------------------------------------------------------------------------------
/test/fixture/block-indent4.css:
--------------------------------------------------------------------------------
1 | body{
2 | height: 10;
3 | width: 2px;
4 | aa:12px;
5 | width: 12px;
6 | }
7 | .CWSIR_entry_icon{display:inline-block;width:33px;height:33px;position:relative;background:url("http://cwsir.sinaapp.com/CWSirExtensions/images/icon.png") no-repeat -120px 0;z-index:10;}
8 |
9 | @media handheld and (min-width:360px),screen and (min-width:480px) {
10 | body {
11 | font-size:large;
12 | }
13 | }
14 |
15 |
16 | @keyframes errorbg {
17 | 0%,
18 | 25% {
19 | background: #f5cfc4;
20 | opacity: .18;
21 | }
22 | 100% {
23 | background: #f5cfc4;
24 | opacity: .18;
25 | }
26 | }
27 |
28 | body ,b,a {
29 | color: #fff;
30 | }
31 |
32 | @media handheld and (min-width:360px), screen and (min-width:480px) {
33 | body {
34 | color: #fff;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/test/fixture/font-sizes.css:
--------------------------------------------------------------------------------
1 | .user-info-list {
2 | height: 90px;
3 | font-size: 13px;
4 | }
5 |
6 | .aa {
7 | height: 90px;
8 | font-size: 13px;
9 | }
10 |
11 | .bb {
12 | height: 90px;
13 | font-size: 13px;
14 | }
15 |
16 | .cc {
17 | height: 90px;
18 | font-size: 13px;
19 | }
20 |
21 | .dd {
22 | height: 90px;
23 | font-size: 13px;
24 | }
25 |
26 | .ee {
27 | height: 90px;
28 | font-size: 13px;
29 | }
30 |
31 | .ff {
32 | height: 90px;
33 | font-size: 13px;
34 | }
35 |
36 | .gg {
37 | height: 90px;
38 | font-size: 13px;
39 | }
40 |
41 | .hh {
42 | height: 90px;
43 | font-size: 13px;
44 | }
45 |
46 | .ii {
47 | height: 90px;
48 | font-size: 13px;
49 | }
50 |
51 | .jj {
52 | height: 90px;
53 | font-size: 13px;
54 | }
55 |
56 | .kk {
57 | height: 90px;
58 | font-size: 13px;
59 | }
60 |
--------------------------------------------------------------------------------
/test/fixture/block-indent1.css:
--------------------------------------------------------------------------------
1 | @keyframes errorbg {
2 | 0%,
3 | 25% {
4 | background: #f5cfc4;
5 | _opacity: .18;
6 | }
7 | 100% {
8 | background: #f5cfc4;
9 | opacity: .18;
10 | }
11 | }
12 |
13 | body ,b,a {
14 | color: #fff;
15 | }
16 |
17 | @media handheld and (min-width:360px), screen and (min-width:480px) {
18 | body {_color: #fff;
19 | }
20 |
21 | }
22 |
23 | @media screen and (orientation:portrait) {
24 | @media screen and (min-device-width: 360px) {
25 | #footerLogo {
26 | *margin-top: 5px;
27 | }
28 | }
29 | @media screen and (min-device-width: 500px) {
30 | #footerLogo {
31 | margin-top: 15px;
32 | }
33 | }
34 | }
35 | @media screen and (orientation:landscape) {
36 | #container {
37 | width: 320px;
38 | margin: 0 auto;
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/test/fixture/inline-comment.css:
--------------------------------------------------------------------------------
1 | /* csshint-disable min-font-size, no-bom */ /* csshint block-indent: [" ", 0] */
2 |
3 | @keyframes errorbg {
4 | 0%,
5 | 25%,
6 | 100% {
7 | background: #f5cfc4;
8 | }
9 | }
10 |
11 | *body {
12 | _margin: 0;
13 | }
14 |
15 | aa {
16 | font-size: 0;
17 | }
18 |
19 |
20 |
21 | @media screen and (min-width:1024px) and (max-width:1280px) {
22 | body {
23 | font-size: medium;
24 | }
25 | }
26 |
27 |
28 | @media screen and (orientation:portrait) {
29 | @media screen and (min-device-width: 360px) {
30 | *body {
31 | margin-top: 5px;
32 | }
33 | }
34 | }
35 |
36 | @media handheld and (min-device-width: 10px) {
37 | body {
38 | *color: #f00;
39 | }
40 | }
41 |
42 | a {
43 | color: #fff;
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/test/fixture/vendor-prefixes-sort.css:
--------------------------------------------------------------------------------
1 | .box {
2 | -moz-box-sizing: border-box;
3 | box-sizing: border-box;
4 | -webkit-box-sizing: border-box;
5 |
6 | }
7 |
8 | .radius {
9 | -webkit-border-radius: 5px;
10 | -moz-border-radius: 5px;
11 | border-radius: 5px;
12 |
13 | }
14 |
15 | .ani {
16 | -webkit-animation: spin 2s infinite linear;
17 | -moz-animation: spin 2s infinite linear;
18 | -ms-animation: spin 2s infinite linear;
19 | -o-animation: spin 2s infinite linear;
20 | animation: spin 2s infinite linear;
21 | color: #fff;
22 | }
23 |
24 |
25 | .shadow {
26 | position: absolute;
27 | width: 170px;
28 | background: #fefefe;
29 | color: #474848;
30 | font-size: 14px;
31 | left: -95px;
32 | top: -125px;
33 | border-radius: 5px;
34 | border-bottom: solid 1px #d0d0cf;
35 | -webkit-box-shadow: 0 0 4px #bebebe;
36 | box-shadow: 0 0 4px #bebebe;
37 | }
38 |
39 | .cc {
40 | -moz-animation: spin 2s infinite linear;
41 | animation: spin 2s infinite linear;
42 | border-radius: 5px;
43 | }
44 |
45 |
46 | .cc {
47 | -webkit-backface-visibility: hidden;
48 | backface-visibility: hidden;
49 | }
50 |
--------------------------------------------------------------------------------
/test/fixture/disallow-overqualified-elements.css:
--------------------------------------------------------------------------------
1 |
2 | /*.a.a {
3 | color: red;
4 | }*/
5 |
6 | /*.c .d {
7 | width: 20px;
8 | }*/
9 |
10 | .aa {
11 | color: red;
12 | }
13 |
14 | p.bb .testp {
15 | color: green;
16 | }
17 |
18 | a:hover {
19 | color: red;
20 | }
21 |
22 | #cc {
23 | color: #fff;
24 | }
25 |
26 | div#cc {
27 | color: #000;
28 | }
29 |
30 | a.test:hover {
31 | color: #ccc;
32 | }
33 |
34 | .aad, a.ddd {
35 | color: red;
36 | }
37 |
38 |
39 |
40 | .post .cc, .page cc.dd, cc.comment cc.ee .gg{
41 | line-height: 1.5;
42 | }
43 |
44 | a.aa {
45 | color: red;
46 | }
47 |
48 | .aad, a {
49 | color: red;
50 | }
51 |
52 | .pagesss .cc, .page .dd{
53 | line-height: 1.5;
54 | }
55 |
56 | .aaa, hh.bbb > ll.oo, .ccc, .ddd{
57 | color: red;
58 | }
59 |
60 | /*.CWSIR_page_tab_radious .CWSIR_page_t_itm, .CWSIR_page_tab_radious .CWSIR_page_t_opt, .CWSIR_page_tab_radious .CWSIR_page_W_vline, .aaa .bbb .ccc {
61 | float: left;
62 | display: inline;
63 | margin: 0 8px 0 0;
64 | line-height: 22px;
65 | text-align: center;
66 | }*/
67 |
68 | /*#ccc .fff .qqq, #ccc .fff .qqq a, #ccc .fff a.cwsirrss_entry-source-title, #ccc .fff a.cwsirrss_entry-post-author-name {
69 | color: #15c;
70 | }*/
71 |
72 |
73 |
74 | /*li.selected > a:hover > a:focus {
75 | color: #ccc;
76 | }*/
77 |
78 |
79 | /*li.selected > a:hover {*/
80 | /*color: red;*/
81 | /*}*/
82 |
--------------------------------------------------------------------------------
/test/fixture/reset.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | /*html,body{height:100%;}*/
3 | /*html{filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);}*/
4 | body, button, input, select, textarea {
5 | font: 12px / 1.125 Arial, Helvetica, sans-serif;
6 | font-family: "宋体";
7 | }
8 |
9 |
10 |
11 | body, h1, h2, h3, h4, h5, h6, dl, dt, dd, ul, ol, li, th, td, p, blockquote, pre, form, fieldset, legend, input, button, textarea, hr, div {
12 | margin: 0;
13 | padding: 0;
14 | }
15 |
16 | table {
17 | border-collapse: collapse;
18 | border-spacing: 0;
19 | }
20 |
21 | li {
22 | list-style: none;
23 | }
24 |
25 | fieldset, img {
26 | border: 0;
27 | }
28 |
29 | q:before, q:after {
30 | content: '';
31 | }
32 |
33 | input, textarea {
34 | outline-style: none;
35 | }
36 |
37 | input[type =
38 | "text"], input[type = "password"], textarea {
39 | outline-style: none;
40 | -webkit-appearance: none;
41 | }
42 |
43 | textarea {
44 | resize: none
45 | }
46 |
47 | address, caption, cite, code, dfn, em, i, th, var {
48 | font-style: normal;
49 | font-weight: normal;
50 | }
51 |
52 | legend {
53 | color: #000;
54 | }
55 |
56 | abbr, acronym {
57 | border: 0;
58 | font-variant: normal;
59 | }
60 |
61 | a {
62 | text-decoration: none;
63 | }
64 |
65 | a:hover {
66 | text-decoration: underline;
67 | }
68 |
--------------------------------------------------------------------------------
/test/fixture/cr.css:
--------------------------------------------------------------------------------
1 | /*.modal-content {
2 | position: relative;
3 | background-color: #fff;
4 | border: 1px solid #999;
5 | border: 1px solid rgba(0, 0, 0, .2);
6 | outline: 0;
7 | -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
8 | box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
9 | -webkit-background-clip: padding-box;
10 | background-clip: padding-box;
11 | }*/
12 |
13 | /*.radius {
14 | -webkit-border-radius: 5px;
15 | border-radius: 5px;
16 | -webkit-animation: spin 2s infinite linear;
17 | animation: spin 2s infinite linear;
18 | }*/
19 |
20 | .modal-content {
21 | -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
22 | box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
23 | }
24 |
25 | @media (min-width: 768px) {
26 | .modal-content {
27 | -webkit-box-shadow: 0 5px 15px rgba(100, 0, 0, .5);
28 | box-shadow: 0 5px 15px rgba(100, 0, 0, .5);
29 | }
30 | @media (min-width: 168px) {
31 | .modal-content {
32 | -webkit-box-shadow: 0 8px 19px rgba(0, 200, 0, .5);
33 | box-shadow: 0 8px 19px rgba(0, 200, 0, .5);
34 | }
35 | }
36 | }
37 |
38 | @-webkit-keyframes link_float {
39 | from {
40 | opacity: 0;
41 | -webkit-transform: scale(0);
42 | transform: scale(0);
43 | }
44 | }
45 | @-moz-keyframes link_float {
46 | from {
47 | -moz-transform: scale(0);
48 | transform: scale(0);
49 | opacity: 0;
50 | }
51 | }
--------------------------------------------------------------------------------
/src/rule/no-bom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file no-bom 的检测逻辑
3 | * 001: [建议] `CSS` 文件使用无 `BOM` 的 `UTF-8` 编码。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | /**
11 | * 当前文件所代表的规则名称
12 | *
13 | * @const
14 | * @type {string}
15 | */
16 | const RULENAME = 'no-bom';
17 |
18 | /**
19 | * 错误的信息
20 | *
21 | * @const
22 | * @type {string}
23 | */
24 | const MSG = 'CSS file should using UTF-8 coding without BOM';
25 |
26 | /**
27 | * 具体的检测逻辑
28 | *
29 | * @param {Object} opts 参数
30 | * @param {*} opts.ruleVal 当前规则具体配置的值
31 | * @param {string} opts.fileContent 文件内容
32 | * @param {string} opts.filePath 文件路径
33 | */
34 | export const check = postcss.plugin(RULENAME, opts =>
35 | (css, result) => {
36 | if (opts.ruleVal) {
37 |
38 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
39 | return;
40 | }
41 |
42 | const bufContent = new Buffer(opts.fileContent, 'utf8');
43 |
44 | const hasBOM
45 | = (bufContent[0] === 0xEF && bufContent[1] === 0xBB && bufContent[2] === 0xBF) // UTF-8 +BOM
46 | || (bufContent[0] === 0xEF && bufContent[1] === 0xBF && bufContent[2] === 0xBD); // unicode UTF16 LE
47 |
48 | if (hasBOM) {
49 | result.warn(RULENAME, {
50 | node: css,
51 | ruleName: RULENAME,
52 | message: MSG,
53 | colorMessage: chalk.grey(MSG)
54 | });
55 | global.CSSHINT_INVALID_ALL_COUNT++;
56 | }
57 | }
58 | }
59 | );
60 |
--------------------------------------------------------------------------------
/src/rule/font-face.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file font-face 的检测逻辑
3 | * Too many different web fonts in the same stylesheet
4 | * https://github.com/CSSLint/csslint/wiki/Don't-use-too-many-web-fonts
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | /**
12 | * 当前文件所代表的规则名称
13 | *
14 | * @const
15 | * @type {string}
16 | */
17 | const RULENAME = 'font-face';
18 |
19 | /**
20 | * 错误的信息
21 | *
22 | * @const
23 | * @type {string}
24 | */
25 | const MSG = '@font-face declarations must not be greater than ';
26 |
27 | let fontFaceCount = 0;
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 | if (!opts.ruleVal || isNaN(opts.ruleVal)) {
40 | return;
41 | }
42 |
43 | fontFaceCount = 0;
44 |
45 | css.walkAtRules(atRule => {
46 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
47 | return;
48 | }
49 |
50 | if (atRule.name === 'font-face') {
51 | fontFaceCount++;
52 | }
53 | });
54 |
55 | if (fontFaceCount > opts.ruleVal) {
56 | const str = MSG + opts.ruleVal + ', current file @font-face declarations is ' + fontFaceCount;
57 | result.warn(RULENAME, {
58 | node: css,
59 | ruleName: RULENAME,
60 | message: str,
61 | colorMessage: chalk.grey(str)
62 | });
63 |
64 | global.CSSHINT_INVALID_ALL_COUNT++;
65 | }
66 | }
67 | );
68 |
--------------------------------------------------------------------------------
/src/rule/font-sizes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file font-sizes 的检测逻辑
3 | * Too many font-size declarations, abstraction needed
4 | * https://github.com/CSSLint/csslint/wiki/Don't-use-too-many-font-size-declarations
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | /**
12 | * 当前文件所代表的规则名称
13 | *
14 | * @const
15 | * @type {string}
16 | */
17 | const RULENAME = 'font-sizes';
18 |
19 | /**
20 | * 错误的信息
21 | *
22 | * @const
23 | * @type {string}
24 | */
25 | const MSG = '`font-size` must not be greater than ';
26 |
27 | let fontSizeCount = 0;
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 | if (!opts.ruleVal || isNaN(opts.ruleVal)) {
40 | return;
41 | }
42 |
43 | fontSizeCount = 0;
44 |
45 | css.walkDecls(decl => {
46 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
47 | return;
48 | }
49 |
50 | const prop = decl.prop;
51 | if (prop === 'font-size') {
52 | fontSizeCount++;
53 | }
54 | });
55 |
56 | if (fontSizeCount > opts.ruleVal) {
57 | const str = MSG + opts.ruleVal + ', current file `font-size` is ' + fontSizeCount;
58 | result.warn(RULENAME, {
59 | node: css,
60 | ruleName: RULENAME,
61 | message: str,
62 | colorMessage: chalk.grey(str)
63 | });
64 |
65 | global.CSSHINT_INVALID_ALL_COUNT++;
66 | }
67 | }
68 | );
69 |
--------------------------------------------------------------------------------
/src/rule/floats.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file floats 的检测逻辑
3 | * Too many floats, you're probably using them for layout. Consider using a grid system instead
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | /**
12 | * 当前文件所代表的规则名称
13 | *
14 | * @const
15 | * @type {string}
16 | */
17 | const RULENAME = 'floats';
18 |
19 | /**
20 | * 错误的信息
21 | *
22 | * @const
23 | * @type {string}
24 | */
25 | const MSG = '`float` must not be greater than ';
26 |
27 | let floatCount = 0;
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 | if (!opts.ruleVal || isNaN(opts.ruleVal)) {
40 | return;
41 | }
42 |
43 | floatCount = 0;
44 |
45 | css.walkDecls(decl => {
46 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
47 | return;
48 | }
49 |
50 | const {prop, value} = decl;
51 | if (prop === 'float' && value !== 'none') {
52 | floatCount++;
53 | }
54 | });
55 |
56 | if (floatCount > opts.ruleVal) {
57 | const str = MSG + opts.ruleVal + ', current file `float` is ' + floatCount;
58 | result.warn(RULENAME, {
59 | node: css,
60 | ruleName: RULENAME,
61 | message: str,
62 | colorMessage: chalk.grey(str)
63 | });
64 |
65 | global.CSSHINT_INVALID_ALL_COUNT++;
66 | }
67 | }
68 | );
69 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 对配置文件的读取合并等等
3 | * @author ielgnaw(wuji0223@gmail.com)
4 | */
5 |
6 | import Manis from 'manis';
7 | import {join} from 'path';
8 | import yaml from 'js-yaml';
9 |
10 | 'use strict';
11 |
12 | let STORAGE = null;
13 |
14 | // const JSON_YAML_REG = /.+\.(json|yml)$/i;
15 |
16 | /**
17 | * 获取 merge 后的配置文件
18 | *
19 | * @param {string} filePath 待检查的文件路径,根据这个路径去寻找用户自定义的配置文件,然后和默认的配置文件 merge
20 | * @param {boolean} refresh 是否强制刷新内存中已经存在的配置
21 | *
22 | * @return {Object} merge 后的配置对象
23 | */
24 | export function loadConfig(filePath, refresh) {
25 | if (!refresh && STORAGE) {
26 | return STORAGE;
27 | }
28 |
29 | let manis = new Manis({
30 | files: [
31 | '.csshintrc'
32 | ],
33 | loader(content) {
34 | if (!content) {
35 | return '';
36 | }
37 | let ret;
38 | try {
39 | ret = yaml.load(content);
40 | }
41 | catch (e) {}
42 | // if (basename(filePath) === '.csshintrc') {
43 | // return JSON.parse(stripJSONComments(content));
44 | // }
45 | //
46 | // let match = filePath.match(JSON_YAML_REG);
47 | //
48 | // if (match) {
49 | // let suffix = match[1];
50 | // if (suffix === 'json') {
51 | // return JSON.parse(stripJSONComments(content));
52 | // }
53 | // else if (suffix === 'yml') {
54 | // return yaml.load(content);
55 | // }
56 | // }
57 | return ret;
58 | }
59 | });
60 |
61 | manis.setDefault(join(__dirname, './csshint.yml'));
62 |
63 | STORAGE = manis.from(filePath);
64 |
65 | return STORAGE;
66 | }
67 |
--------------------------------------------------------------------------------
/test/fixture/font-face.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'MyFontFamily';
3 | src: url('myfont-webfont.eot?#iefix') format('embedded-opentype'),
4 | url('myfont-webfont.woff') format('woff'),
5 | url('myfont-webfont.ttf') format('truetype'),
6 | url('myfont-webfont.svg#svgFontName') format('svg');
7 | }
8 |
9 | @font-face {
10 | font-family: 'HarlowSolid';
11 | src: url('harlowsi-webfont.eot?') format('eot'),
12 | url('harlowsi-webfont.woff') format('woff'),
13 | url('harlowsi-webfont.ttf') format('truetype'),
14 | url('harlowsi-webfont.svg#harlowsi-webfont') format('svg');
15 | }
16 |
17 | @font-face {
18 | font-family: 'Test';
19 | src: url('harlowsi-webfont.eot?') format('eot'),
20 | url('harlowsi-webfont.woff') format('woff'),
21 | url('harlowsi-webfont.ttf') format('truetype'),
22 | url('harlowsi-webfont.svg#harlowsi-webfont') format('svg');
23 | }
24 |
25 | @font-face {
26 | font-family: 'HarlowSolid';
27 | src: url('harlowsi-webfont.eot?') format('eot'),
28 | url('harlowsi-webfont.woff') format('woff'),
29 | url('harlowsi-webfont.ttf') format('truetype'),
30 | url('harlowsi-webfont.svg#harlowsi-webfont') format('svg');
31 | }
32 |
33 | @font-face {
34 | font-family: 'Test';
35 | src: url('harlowsi-webfont.eot?') format('eot'),
36 | url('harlowsi-webfont.woff') format('woff'),
37 | url('harlowsi-webfont.ttf') format('truetype'),
38 | url('harlowsi-webfont.svg#harlowsi-webfont') format('svg');
39 | }
40 |
41 |
42 | @font-face {
43 | font-family: 'HarlowSolid';
44 | src: url('harlowsi-webfont.eot?') format('eot'),
45 | url('harlowsi-webfont.woff') format('woff'),
46 | url('harlowsi-webfont.ttf') format('truetype'),
47 | url('harlowsi-webfont.svg#harlowsi-webfont') format('svg');
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/src/rule/import.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file import 的检测逻辑
3 | * Don't use @import, use instead
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-@import
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'import';
20 |
21 | /**
22 | * 错误的信息
23 | *
24 | * @const
25 | * @type {string}
26 | */
27 | const MSG = 'Don\'t use @import, use instead';
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 |
40 | if (!opts.ruleVal) {
41 | return;
42 | }
43 |
44 | css.walkAtRules(atRule => {
45 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
46 | return;
47 | }
48 |
49 | if (atRule.name === 'import') {
50 | const source = atRule.source;
51 | const line = source.start.line;
52 | const lineContent = getLineContent(line, source.input.css);
53 | const col = source.start.column;
54 | result.warn(RULENAME, {
55 | node: atRule,
56 | ruleName: RULENAME,
57 | line: line,
58 | col: col,
59 | message: MSG,
60 | colorMessage: '`'
61 | + lineContent.replace(/@import/g, chalk.magenta('@import'))
62 | + '` '
63 | + chalk.grey(MSG)
64 | });
65 | global.CSSHINT_INVALID_ALL_COUNT++;
66 | }
67 | });
68 | }
69 | );
70 |
--------------------------------------------------------------------------------
/src/rule/max-length.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file max-length 的检测逻辑
3 | * 006: [强制] 每行不得超过 `120` 个字符,除非单行不可分割。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | /**
11 | * 当前文件所代表的规则名称
12 | *
13 | * @const
14 | * @type {string}
15 | */
16 | const RULENAME = 'max-length';
17 |
18 | /**
19 | * 匹配 css 属性值的 url(...);
20 | *
21 | * @const
22 | * @type {RegExp}
23 | */
24 | const PATTERN_URI = /url\(["']?([^\)"']+)["']?\)/i;
25 |
26 | let excludeLines = [];
27 |
28 | /**
29 | * 具体的检测逻辑
30 | *
31 | * @param {Object} opts 参数
32 | * @param {*} opts.ruleVal 当前规则具体配置的值
33 | * @param {string} opts.fileContent 文件内容
34 | * @param {string} opts.filePath 文件路径
35 | */
36 | export const check = postcss.plugin(RULENAME, opts =>
37 | (css, result) => {
38 | if (opts.ruleVal) {
39 |
40 | excludeLines = [];
41 |
42 | const MSG = 'Each line must not be greater than ' + opts.ruleVal + ' characters';
43 |
44 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
45 | return;
46 | }
47 |
48 | // 排除掉 background-image: 2px 2px url(data:image/gif;base64,.....); 的情况
49 | css.walkDecls(decl => {
50 | const value = decl.value;
51 | if (PATTERN_URI.test(value)) {
52 | excludeLines.push(decl.source.start.line);
53 | }
54 | });
55 |
56 | const lines = css.source.input.css.split(/\n/);
57 |
58 | for (let i = 0, len = lines.length; i < len; i++) {
59 | if (lines[i].length > opts.ruleVal
60 | && excludeLines.indexOf(i + 1) === -1
61 | ) {
62 | result.warn(RULENAME, {
63 | node: css,
64 | ruleName: RULENAME,
65 | line: i + 1,
66 | message: MSG,
67 | colorMessage: chalk.grey(MSG)
68 | });
69 | global.CSSHINT_INVALID_ALL_COUNT++;
70 | }
71 | }
72 | }
73 | }
74 | );
75 |
--------------------------------------------------------------------------------
/src/rule/star-property-hack.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file star-property-hack 的检测逻辑
3 | * Checks for the star property hack (targets IE6/7)
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-star-hack
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'star-property-hack';
19 |
20 | /**
21 | * 错误的信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'Disallow properties with a star prefix';
27 |
28 | /**
29 | * 具体的检测逻辑
30 | *
31 | * @param {Object} opts 参数
32 | * @param {*} opts.ruleVal 当前规则具体配置的值
33 | * @param {string} opts.fileContent 文件内容
34 | * @param {string} opts.filePath 文件路径
35 | */
36 | export const check = postcss.plugin(RULENAME, opts =>
37 | (css, result) => {
38 |
39 | if (opts.ruleVal) {
40 |
41 | css.walkDecls(decl => {
42 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
43 | return;
44 | }
45 |
46 | const before = decl.raws.before;
47 |
48 | if (before.slice(-1) === '*') {
49 | const source = decl.source;
50 | const line = source.start.line;
51 | const lineContent = getLineContent(line, source.input.css);
52 | const col = source.start.column;
53 | result.warn(RULENAME, {
54 | node: decl,
55 | ruleName: RULENAME,
56 | line: line,
57 | col: col,
58 | message: MSG,
59 | colorMessage: '`'
60 | + changeColorByStartAndEndIndex(
61 | lineContent, col, source.end.column
62 | )
63 | + '` '
64 | });
65 |
66 | global.CSSHINT_INVALID_ALL_COUNT++;
67 | }
68 | });
69 | }
70 | }
71 | );
72 |
--------------------------------------------------------------------------------
/src/rule/underscore-property-hack.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file underscore-property-hack 的检测逻辑
3 | * Checks for the underscore property hack (targets IE6)
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'underscore-property-hack';
19 |
20 | /**
21 | * 错误的信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'Disallow properties with a underscore prefix';
27 |
28 | /**
29 | * 具体的检测逻辑
30 | *
31 | * @param {Object} opts 参数
32 | * @param {*} opts.ruleVal 当前规则具体配置的值
33 | * @param {string} opts.fileContent 文件内容
34 | * @param {string} opts.filePath 文件路径
35 | */
36 | export const check = postcss.plugin(RULENAME, opts =>
37 | (css, result) => {
38 |
39 | if (opts.ruleVal) {
40 |
41 | css.walkDecls(decl => {
42 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
43 | return;
44 | }
45 |
46 | const before = decl.raws.before;
47 |
48 | if (before.slice(-1) === '_') {
49 | const source = decl.source;
50 | const line = source.start.line;
51 | const lineContent = getLineContent(line, source.input.css);
52 | const col = source.start.column;
53 | result.warn(RULENAME, {
54 | node: decl,
55 | ruleName: RULENAME,
56 | line: line,
57 | col: col,
58 | message: MSG,
59 | colorMessage: '`'
60 | + changeColorByStartAndEndIndex(
61 | lineContent, col, source.end.column
62 | )
63 | + '` '
64 | });
65 |
66 | global.CSSHINT_INVALID_ALL_COUNT++;
67 | }
68 | });
69 | }
70 | }
71 | );
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "csshint",
3 | "description": "lint your css code",
4 | "version": "0.3.4",
5 | "keywords": [
6 | "csslint",
7 | "csshint"
8 | ],
9 | "maintainers": [
10 | {
11 | "name": "ielgnaw",
12 | "email": "wuji0223@gmail.com"
13 | }
14 | ],
15 | "dependencies": {
16 | "chalk": "^1.1.3",
17 | "edp-core": "^1.0.32",
18 | "js-yaml": "^3.6.1",
19 | "manis": "^0.3.0",
20 | "object-assign": "^4.1.0",
21 | "postcss": "^5.2.0",
22 | "strip-json-comments": "^2.0.1"
23 | },
24 | "engines": {
25 | "node": ">=0.10"
26 | },
27 | "devDependencies": {
28 | "babel-cli": "^6.14.0",
29 | "babel-core": "^6.14.0",
30 | "babel-istanbul": "^0.11.0",
31 | "babel-node-debug": "^2.0.0",
32 | "babel-preset-es2015": "^6.14.0",
33 | "babel-preset-stage-2": "^6.13.0",
34 | "chai": "^3.5.0",
35 | "coveralls": "^2.11.13",
36 | "debug": "^2.2.0",
37 | "fecs": "stable",
38 | "istanbul": "^0.3.2",
39 | "jasmine-node": "^1.14.5",
40 | "json-stringify-safe": "^5.0.1",
41 | "mocha": "^3.0.2"
42 | },
43 | "scripts": {
44 | "lint": "fecs src test/**/*.spec.js --type=js",
45 | "compile": "rm -rf lib && ./node_modules/.bin/babel src -d lib --source-maps inline --copy-files",
46 | "debug": "npm run compile && ./node_modules/.bin/babel-node-debug lib/index.js",
47 | "test": "npm run compile && ./node_modules/.bin/_mocha --compilers js:babel-core/register --recursive",
48 | "coverage": "npm run compile && ./node_modules/.bin/babel-node ./node_modules/.bin/babel-istanbul cover _mocha 'test/**/*.spec.@(js|es|es6)'",
49 | "coverage1": "npm run compile && ./node_modules/.bin/babel-node ./node_modules/.bin/babel-istanbul cover _mocha -- --recursive",
50 | "coveralls": "cat ./coverage/lcov.info | coveralls",
51 | "sourcemap": "./node_modules/.bin/babel src -d lib -s",
52 | "watch": "./node_modules/.bin/babel -w src -d lib --source-maps inline --copy-files",
53 | "prepublish": "npm run compile"
54 | },
55 | "main": "./lib/checker.js",
56 | "bin": {
57 | "csshint": "./bin/csshint-cli"
58 | },
59 | "repository": {
60 | "type": "git",
61 | "url": "git@github.com:ecomfe/node-csshint"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/rule/require-transition-property.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file require-transition-property 的检测逻辑
3 | * 041: [强制] 使用 `transition` 时应指定 `transition-property`。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'require-transition-property';
19 |
20 | /**
21 | * 错误的信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'When using the `transition`, `transition-property` should be specified';
27 |
28 | /**
29 | * 具体的检测逻辑
30 | *
31 | * @param {Object} opts 参数
32 | * @param {*} opts.ruleVal 当前规则具体配置的值
33 | * @param {string} opts.fileContent 文件内容
34 | * @param {string} opts.filePath 文件路径
35 | */
36 | export const check = postcss.plugin(RULENAME, opts =>
37 | (css, result) => {
38 |
39 | if (opts.ruleVal) {
40 |
41 | css.walkDecls(decl => {
42 |
43 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
44 | return;
45 | }
46 |
47 | const prop = decl.prop;
48 |
49 | if (prop === 'transition') {
50 | const parts = postcss.list.space(decl.value);
51 | if (parts.indexOf('all') > -1) {
52 | const source = decl.source;
53 | const line = source.start.line;
54 | const lineContent = getLineContent(line, source.input.css);
55 | result.warn(RULENAME, {
56 | node: decl,
57 | ruleName: RULENAME,
58 | line: line,
59 | message: MSG,
60 | colorMessage: '`'
61 | + lineContent.replace(/\ball\b/g, chalk.magenta('all'))
62 | + '` '
63 | + chalk.grey(MSG)
64 | });
65 | global.CSSHINT_INVALID_ALL_COUNT++;
66 | }
67 | }
68 | });
69 | }
70 | }
71 | );
72 |
--------------------------------------------------------------------------------
/src/rule/box-sizing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file box-sizing 的检测逻辑
3 | * The box-sizing properties isn't supported in IE6 and IE7
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'box-sizing';
20 |
21 | /**
22 | * 错误的信息
23 | *
24 | * @const
25 | * @type {string}
26 | */
27 | const MSG = 'The box-sizing properties isn\'t supported in IE6 and IE7';
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 | if (!opts.ruleVal) {
40 | return;
41 | }
42 |
43 | css.walkRules(rule => {
44 | rule.walkDecls(decl => {
45 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
46 | return;
47 | }
48 |
49 | const prop = decl.prop;
50 | if (prop === 'box-sizing') {
51 | const source = decl.source;
52 | const line = source.start.line;
53 | const lineContent = getLineContent(line, source.input.css);
54 | const col = source.start.column;
55 | result.warn(RULENAME, {
56 | node: rule,
57 | ruleName: RULENAME,
58 | line: line,
59 | col: col,
60 | message: MSG,
61 | colorMessage: '`'
62 | + lineContent.replace(
63 | prop,
64 | chalk.magenta(prop)
65 | )
66 | + '` '
67 | + chalk.grey(MSG)
68 | });
69 | global.CSSHINT_INVALID_ALL_COUNT++;
70 | }
71 | });
72 | });
73 | }
74 | );
75 |
--------------------------------------------------------------------------------
/src/rule/empty-rules.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file empty-rules 的检测逻辑
3 | * Rules without any properties specified should be removed
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'empty-rules';
20 |
21 | let propertyCount = 0;
22 |
23 | /**
24 | * 错误的信息
25 | *
26 | * @const
27 | * @type {string}
28 | */
29 | const MSG = 'Rules without any properties specified should be removed';
30 |
31 | /**
32 | * 具体的检测逻辑
33 | *
34 | * @param {Object} opts 参数
35 | * @param {*} opts.ruleVal 当前规则具体配置的值
36 | * @param {string} opts.fileContent 文件内容
37 | * @param {string} opts.filePath 文件路径
38 | */
39 | export const check = postcss.plugin(RULENAME, opts =>
40 | (css, result) => {
41 | if (!opts.ruleVal) {
42 | return;
43 | }
44 |
45 | css.walkRules(rule => {
46 | propertyCount = 0;
47 |
48 | rule.walkDecls(() => {
49 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
50 | return;
51 | }
52 | propertyCount++;
53 | });
54 |
55 | if (propertyCount === 0) {
56 | const source = rule.source;
57 | const line = source.start.line;
58 | const lineContent = getLineContent(line, source.input.css);
59 | const col = source.start.column;
60 | result.warn(RULENAME, {
61 | node: rule,
62 | ruleName: RULENAME,
63 | line: line,
64 | col: col,
65 | message: MSG,
66 | colorMessage: '`'
67 | + lineContent.replace(
68 | rule.selector,
69 | chalk.magenta(rule.selector)
70 | )
71 | + '` '
72 | + chalk.grey(MSG)
73 | });
74 | global.CSSHINT_INVALID_ALL_COUNT++;
75 | }
76 | });
77 | }
78 | );
79 |
--------------------------------------------------------------------------------
/src/rule/duplicate-properties.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file duplicate-properties 的检测逻辑
3 | * Duplicate properties must appear one after the other
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'duplicate-properties';
20 |
21 | const MSG = 'Duplicate properties must appear one after the other';
22 |
23 | let properties = {};
24 | let lastProperty = '';
25 |
26 | /**
27 | * 具体的检测逻辑
28 | *
29 | * @param {Object} opts 参数
30 | * @param {*} opts.ruleVal 当前规则具体配置的值
31 | * @param {string} opts.fileContent 文件内容
32 | * @param {string} opts.filePath 文件路径
33 | */
34 | export const check = postcss.plugin(RULENAME, opts =>
35 | (css, result) => {
36 | if (!opts.ruleVal) {
37 | return;
38 | }
39 |
40 | css.walkRules(rule => {
41 | properties = {};
42 |
43 | rule.walkDecls(decl => {
44 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
45 | return;
46 | }
47 |
48 | const {prop, value} = decl;
49 | if (properties[prop] && (lastProperty !== prop || properties[prop] === value)) {
50 | const source = decl.source;
51 | const line = source.start.line;
52 | const lineContent = getLineContent(line, source.input.css);
53 | const col = source.start.column;
54 | result.warn(RULENAME, {
55 | node: decl,
56 | ruleName: RULENAME,
57 | line: line,
58 | col: col,
59 | message: MSG,
60 | colorMessage: '`'
61 | + chalk.magenta(lineContent)
62 | + '` '
63 | + chalk.grey(MSG)
64 | });
65 | global.CSSHINT_INVALID_ALL_COUNT++;
66 | }
67 |
68 | properties[prop] = value;
69 | lastProperty = prop;
70 | });
71 | });
72 | }
73 | );
74 |
--------------------------------------------------------------------------------
/src/rule/horizontal-vertical-position.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file horizontal-vertical-position 的检测逻辑
3 | * 033: [强制] 必须同时给出水平和垂直方向的位置。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'horizontal-vertical-position';
19 |
20 | /**
21 | * 错误的信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'Must give the horizontal and vertical position';
27 |
28 | /**
29 | * 具体的检测逻辑
30 | *
31 | * @param {Object} opts 参数
32 | * @param {*} opts.ruleVal 当前规则具体配置的值
33 | * @param {string} opts.fileContent 文件内容
34 | * @param {string} opts.filePath 文件路径
35 | */
36 | export const check = postcss.plugin(RULENAME, opts =>
37 | (css, result) => {
38 |
39 | if (opts.ruleVal) {
40 |
41 | css.walkDecls(decl => {
42 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
43 | return;
44 | }
45 |
46 | if (decl.prop === 'background-position') {
47 | const parts = postcss.list.space(decl.value);
48 | if (parts.length < 2) {
49 | const source = decl.source;
50 | const line = source.start.line;
51 | const lineContent = getLineContent(line, source.input.css);
52 | const col = source.start.column + decl.prop.length + decl.raws.between.length;
53 | result.warn(RULENAME, {
54 | node: decl,
55 | ruleName: RULENAME,
56 | line: line,
57 | col: col,
58 | message: MSG,
59 | colorMessage: '`'
60 | + changeColorByStartAndEndIndex(
61 | lineContent, col, source.end.column
62 | )
63 | + '` '
64 | + chalk.grey(MSG)
65 | });
66 | global.CSSHINT_INVALID_ALL_COUNT++;
67 | }
68 | }
69 | });
70 | }
71 | }
72 | );
73 |
--------------------------------------------------------------------------------
/src/rule/disallow-quotes-in-url.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file disallow-quotes-in-url 的检测逻辑
3 | * 026: [强制] `url()` 函数中的路径不加引号。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'disallow-quotes-in-url';
19 |
20 | /**
21 | * 匹配 css 中 url 的正则
22 | *
23 | * @const
24 | * @type {RegExp}
25 | */
26 | const PATTERN_URL = /\burl\s*\((["']?)([^\)]+)\1\)/g;
27 |
28 | /**
29 | * 错误信息
30 | *
31 | * @const
32 | * @type {string}
33 | */
34 | const MSG = 'Path in the `url()` must without the quotes';
35 |
36 | /**
37 | * 具体的检测逻辑
38 | *
39 | * @param {Object} opts 参数
40 | * @param {*} opts.ruleVal 当前规则具体配置的值
41 | * @param {string} opts.fileContent 文件内容
42 | * @param {string} opts.filePath 文件路径
43 | */
44 | export const check = postcss.plugin(RULENAME, opts =>
45 | (css, result) => {
46 | if (opts.ruleVal) {
47 |
48 | css.walkDecls(decl => {
49 |
50 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
51 | return;
52 | }
53 |
54 | const {source, value} = decl;
55 | const line = source.start.line;
56 | const lineContent = getLineContent(line, source.input.css);
57 |
58 | let match = null;
59 | /* eslint-disable no-extra-boolean-cast */
60 | while (!!(match = PATTERN_URL.exec(value))) {
61 | if (match[1]) {
62 | result.warn(RULENAME, {
63 | node: decl,
64 | ruleName: RULENAME,
65 | line: line,
66 | col: lineContent.indexOf(match[0]) + 1,
67 | message: MSG,
68 | colorMessage: '`'
69 | + lineContent.replace(match[0], chalk.magenta(match[0]))
70 | + '` '
71 | + chalk.grey(MSG)
72 | });
73 | global.CSSHINT_INVALID_ALL_COUNT++;
74 | }
75 | }
76 | /* eslint-enable no-extra-boolean-cast */
77 | });
78 | }
79 | }
80 | );
81 |
--------------------------------------------------------------------------------
/src/rule/disallow-expression.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file disallow-expression 的检测逻辑
3 | * 050: [强制] 禁止使用 `Expression`。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent} from '../util';
11 |
12 | /**
13 | * 匹配 css 表达式的正则
14 | *
15 | * @const
16 | * @type {RegExp}
17 | */
18 | const PATTERN_EXP = /expression\(/i;
19 |
20 | /**
21 | * 当前文件所代表的规则名称
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const RULENAME = 'disallow-expression';
27 |
28 | /**
29 | * 错误的信息
30 | *
31 | * @const
32 | * @type {string}
33 | */
34 | const MSG = 'Disallow use `Expression`';
35 |
36 | /**
37 | * 具体的检测逻辑
38 | *
39 | * @param {Object} opts 参数
40 | * @param {*} opts.ruleVal 当前规则具体配置的值
41 | * @param {string} opts.fileContent 文件内容
42 | * @param {string} opts.filePath 文件路径
43 | */
44 | export const check = postcss.plugin(RULENAME, opts =>
45 | (css, result) => {
46 | if (opts.ruleVal) {
47 | css.walkDecls(decl => {
48 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
49 | return;
50 | }
51 |
52 | const parts = postcss.list.space(decl.value);
53 | for (let i = 0, len = parts.length; i < len; i++) {
54 | const part = parts[i];
55 | if (PATTERN_EXP.test(part)) {
56 | const source = decl.source;
57 | const line = source.start.line;
58 | const lineContent = getLineContent(line, source.input.css);
59 | const col = source.start.column + decl.prop.length + decl.raws.between.length;
60 | result.warn(RULENAME, {
61 | node: decl,
62 | ruleName: RULENAME,
63 | line: line,
64 | col: col,
65 | message: MSG,
66 | colorMessage: '`'
67 | + lineContent.replace(/expression/g, chalk.magenta('expression'))
68 | + '` '
69 | + chalk.grey(MSG)
70 | });
71 | global.CSSHINT_INVALID_ALL_COUNT++;
72 | continue;
73 | }
74 | }
75 |
76 | });
77 | }
78 | }
79 | );
80 |
--------------------------------------------------------------------------------
/src/rule/require-before-space.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file require-before-space 的检测逻辑
3 | * `{` 对应 003: [强制] `选择器` 与 `{` 之间必须包含空格。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'require-before-space';
19 |
20 | /**
21 | * 错误的信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'Must contain spaces before the `{`';
27 |
28 | const arrayProto = Array.prototype;
29 |
30 | /**
31 | * 具体的检测逻辑
32 | *
33 | * @param {Object} opts 参数
34 | * @param {*} opts.ruleVal 当前规则具体配置的值
35 | * @param {string} opts.fileContent 文件内容
36 | * @param {string} opts.filePath 文件路径
37 | */
38 | export const check = postcss.plugin(RULENAME, opts =>
39 | (css, result) => {
40 | const ruleVal = opts.ruleVal;
41 | const realRuleVal = [];
42 | arrayProto.push[Array.isArray(ruleVal) ? 'apply' : 'call'](realRuleVal, ruleVal);
43 |
44 | if (realRuleVal.length) {
45 | css.walkRules(rule => {
46 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
47 | return;
48 | }
49 |
50 | // 只有 { 时,才能用 between 处理,其他符号的 require-before-space 规则还未实现
51 | if (rule.raws.between === '' && realRuleVal.indexOf('{') !== -1) {
52 | const source = rule.source;
53 | const line = source.start.line;
54 | const col = source.start.column + rule.selector.length;
55 | const lineContent = getLineContent(line, source.input.css) || '';
56 | result.warn(RULENAME, {
57 | node: rule,
58 | ruleName: RULENAME,
59 | errorChar: '{',
60 | line: line,
61 | col: col,
62 | message: MSG,
63 | colorMessage: '`'
64 | + lineContent.replace(
65 | '{',
66 | chalk.magenta('{')
67 | )
68 | + '` '
69 | + chalk.grey(MSG)
70 | });
71 | global.CSSHINT_INVALID_ALL_COUNT++;
72 | }
73 | });
74 | }
75 | }
76 | );
77 |
--------------------------------------------------------------------------------
/src/rule/disallow-important.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file disallow-important 的检测逻辑
3 | * 019: [建议] 尽量不使用 `!important` 声明。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'disallow-important';
19 |
20 | /**
21 | * 错误的信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'Try not to use the `important` statement';
27 |
28 | /**
29 | * 记录行号的临时变量,例如
30 | * color:red !important;height: 100px !important;
31 | * 这段 css ,希望的是这一行只报一次 !important 的错误,这一次把这一行里面的 !important 全部高亮
32 | *
33 | * @type {number}
34 | */
35 | let lineCache = 0;
36 |
37 | /**
38 | * 具体的检测逻辑
39 | *
40 | * @param {Object} opts 参数
41 | * @param {*} opts.ruleVal 当前规则具体配置的值
42 | * @param {string} opts.fileContent 文件内容
43 | * @param {string} opts.filePath 文件路径
44 | */
45 | export const check = postcss.plugin(RULENAME, opts =>
46 | (css, result) => {
47 | if (opts.ruleVal) {
48 |
49 | lineCache = 0;
50 |
51 | css.walkDecls(decl => {
52 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
53 | return;
54 | }
55 | if (decl.important) {
56 | const source = decl.source;
57 | const line = source.start.line;
58 |
59 | // lineCache === line 时,说明是同一行的,那么就不报了
60 | if (lineCache !== line) {
61 | lineCache = line;
62 | const lineContent = getLineContent(line, source.input.css) || '';
63 | result.warn(RULENAME, {
64 | node: decl,
65 | ruleName: RULENAME,
66 | line: line,
67 | message: MSG,
68 | colorMessage: '`'
69 | + lineContent.replace(
70 | /!important/gi,
71 | chalk.magenta('!important')
72 | )
73 | + '` '
74 | + chalk.grey(MSG)
75 | });
76 | global.CSSHINT_INVALID_ALL_COUNT++;
77 | }
78 | }
79 | });
80 | }
81 | }
82 | );
83 |
--------------------------------------------------------------------------------
/src/rule/always-semicolon.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file always-semicolon 的检测逻辑
3 | * 012: [强制] 属性定义后必须以分号结尾。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'always-semicolon';
19 |
20 | /**
21 | * 错误信息
22 | *
23 | * @const
24 | * @type {string}
25 | */
26 | const MSG = 'Attribute definition must end with a semicolon';
27 |
28 | /**
29 | * 具体的检测逻辑
30 | *
31 | * @param {Object} opts 参数
32 | * @param {*} opts.ruleVal 当前规则具体配置的值
33 | * @param {string} opts.fileContent 文件内容
34 | * @param {string} opts.filePath 文件路径
35 | */
36 | export const check = postcss.plugin(RULENAME, opts =>
37 | (css, result) => {
38 | if (!opts.ruleVal) {
39 | return;
40 | }
41 |
42 | css.walkRules(rule => {
43 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
44 | return;
45 | }
46 |
47 | if (rule.raws.semicolon) {
48 | return;
49 | }
50 |
51 | const lastProp = rule.nodes[rule.nodes.length - 1];
52 | if (lastProp && lastProp.type !== 'comment') {
53 | const source = lastProp.source;
54 | const line = source.start.line;
55 | const lineContent = getLineContent(line, source.input.css) || '';
56 |
57 | const value = lastProp.important
58 | ? lastProp.value + (lastProp.important ? lastProp.important : ' !important')
59 | : lastProp.value;
60 |
61 | const colorStr = lastProp.prop + lastProp.raws.between + value;
62 | const col = source.start.column + colorStr.length;
63 |
64 | result.warn(RULENAME, {
65 | node: rule,
66 | ruleName: RULENAME,
67 | line: line,
68 | col: col,
69 | message: MSG,
70 | colorMessage: '`'
71 | + lineContent.replace(
72 | colorStr,
73 | chalk.magenta(colorStr)
74 | )
75 | + '` '
76 | + chalk.grey(MSG)
77 | });
78 | global.CSSHINT_INVALID_ALL_COUNT++;
79 | }
80 | });
81 | }
82 | );
83 |
--------------------------------------------------------------------------------
/src/rule/adjoining-classes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file adjoining-classes 的检测逻辑
3 | * Don't use adjoining classes 例如 .foo.bar
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent} from '../util';
12 |
13 | 'use strict';
14 |
15 | /**
16 | * 当前文件所代表的规则名称
17 | *
18 | * @const
19 | * @type {string}
20 | */
21 | const RULENAME = 'adjoining-classes';
22 |
23 | /**
24 | * css 组合的正则匹配
25 | *
26 | * @const
27 | * @type {RegExp}
28 | */
29 | const PATTERN_COMBINATORS = /[\s>+~,[]+/;
30 |
31 | /**
32 | * 错误信息
33 | *
34 | * @const
35 | * @type {string}
36 | */
37 | const MSG = 'Don\'t use adjoining classes';
38 |
39 | /**
40 | * 具体的检测逻辑
41 | *
42 | * @param {Object} opts 参数
43 | * @param {*} opts.ruleVal 当前规则具体配置的值
44 | * @param {string} opts.fileContent 文件内容
45 | * @param {string} opts.filePath 文件路径
46 | */
47 | export const check = postcss.plugin(RULENAME, opts =>
48 | (css, result) => {
49 | if (!opts.ruleVal) {
50 | return;
51 | }
52 |
53 | css.walkRules(rule => {
54 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
55 | return;
56 | }
57 |
58 | const segments = rule.selector.split(PATTERN_COMBINATORS);
59 |
60 | for (let i = 0, len = segments.length; i < len; i++) {
61 | const segment = segments[i];
62 | if (segment.split('.').length > 2) {
63 | const source = rule.source;
64 | const line = source.start.line;
65 | const lineContent = getLineContent(line, source.input.css) || '';
66 | const colorStr = segment;
67 | result.warn(RULENAME, {
68 | node: rule,
69 | ruleName: RULENAME,
70 | line: line,
71 | message: MSG,
72 | colorMessage: '`'
73 | + lineContent.replace(
74 | colorStr,
75 | chalk.magenta(colorStr)
76 | )
77 | + '` '
78 | + chalk.grey(MSG)
79 | });
80 | global.CSSHINT_INVALID_ALL_COUNT++;
81 | }
82 | }
83 | });
84 | }
85 | );
86 |
--------------------------------------------------------------------------------
/src/rule/property-not-existed.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file property-not-existed 的检测逻辑,检测属性是否存在
3 | * @author ielgnaw(wuji0223@gmail.com)
4 | */
5 |
6 | import chalk from 'chalk';
7 | import postcss from 'postcss';
8 |
9 | import {getPrefixList} from '../prefixes';
10 |
11 | const prefixList = getPrefixList();
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'property-not-existed';
20 |
21 | /**
22 | * 具体的检测逻辑
23 | *
24 | * @param {Object} opts 参数
25 | * @param {*} opts.ruleVal 当前规则具体配置的值
26 | * @param {string} opts.fileContent 文件内容
27 | * @param {string} opts.filePath 文件路径
28 | */
29 | export const check = postcss.plugin(RULENAME, opts =>
30 | (css, result) => {
31 |
32 | if (opts.ruleVal) {
33 |
34 | css.walkDecls(decl => {
35 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
36 | return;
37 | }
38 |
39 | const prop = decl.prop;
40 | const standardProperty = prop.replace(/^\-(webkit|moz|ms|o)\-/g, '');
41 | // 标准模式在 prefixList 中,那么如果 propertyName 不在 prefixList 中
42 | // 即这个属性用错了,例如 -o-animation
43 | if (prefixList.indexOf(standardProperty) > -1) {
44 | if (prefixList.indexOf(prop) <= -1) {
45 |
46 | const source = decl.source;
47 | const line = source.start.line;
48 | const col = source.start.column;
49 |
50 | result.warn(RULENAME, {
51 | node: decl,
52 | ruleName: RULENAME,
53 | line: line,
54 | col: col,
55 | message: ''
56 | + 'Current property '
57 | + '`'
58 | + prop
59 | + '` '
60 | + 'is not existed',
61 | colorMessage: ''
62 | + 'Current property '
63 | + '`'
64 | + chalk.magenta(prop)
65 | + '` '
66 | + 'is not existed'
67 | });
68 | global.CSSHINT_INVALID_ALL_COUNT++;
69 | }
70 | }
71 | });
72 | }
73 | }
74 | );
75 |
--------------------------------------------------------------------------------
/src/rule/disallow-named-color.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file disallow-named-color 的检测逻辑
3 | * 031: [强制] 颜色值不允许使用命名色值。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
11 | import colors from '../colors';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'disallow-named-color';
20 |
21 | /**
22 | * 错误的信息
23 | *
24 | * @const
25 | * @type {string}
26 | */
27 | const MSG = 'Color values using named color value is not allowed';
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 |
40 | if (opts.ruleVal) {
41 |
42 | css.walkDecls(decl => {
43 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
44 | return;
45 | }
46 |
47 | const parts = postcss.list.space(decl.value);
48 | for (let i = 0, len = parts.length; i < len; i++) {
49 | const part = parts[i];
50 | if (colors.hasOwnProperty(part)) {
51 | const source = decl.source;
52 | const line = source.start.line;
53 | const lineContent = getLineContent(line, source.input.css);
54 | const extraLine = decl.value.indexOf(part) || 0;
55 | const col = source.start.column + decl.prop.length + decl.raws.between.length + extraLine;
56 | result.warn(RULENAME, {
57 | node: decl,
58 | ruleName: RULENAME,
59 | line: line,
60 | col: col,
61 | message: MSG,
62 | colorMessage: '`'
63 | + changeColorByStartAndEndIndex(
64 | lineContent, col, source.end.column
65 | )
66 | + '` '
67 | + chalk.grey(MSG)
68 | });
69 | global.CSSHINT_INVALID_ALL_COUNT++;
70 | }
71 | }
72 | });
73 | }
74 | }
75 | );
76 |
--------------------------------------------------------------------------------
/src/rule/hex-color.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file hex-color 的检测逻辑
3 | * 029: [强制] RGB颜色值必须使用十六进制记号形式 `#rrggbb`。不允许使用 `rgb()`。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'hex-color';
19 |
20 | /**
21 | * 匹配 rgb, hsl 颜色表达式的正则
22 | *
23 | * @const
24 | * @type {RegExp}
25 | */
26 | const PATTERN_COLOR_EXP = /(\brgb\b|\bhsl\b)/gi;
27 |
28 | /**
29 | * 错误的信息
30 | *
31 | * @const
32 | * @type {string}
33 | */
34 | const MSG = ''
35 | + 'Color value must use the sixteen hexadecimal mark forms such as `#RGB`.'
36 | + ' Don\'t use RGB、HSL expression';
37 |
38 | /**
39 | * 具体的检测逻辑
40 | *
41 | * @param {Object} opts 参数
42 | * @param {*} opts.ruleVal 当前规则具体配置的值
43 | * @param {string} opts.fileContent 文件内容
44 | * @param {string} opts.filePath 文件路径
45 | */
46 | export const check = postcss.plugin(RULENAME, opts =>
47 | (css, result) => {
48 |
49 | if (opts.ruleVal) {
50 |
51 | css.walkDecls(decl => {
52 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
53 | return;
54 | }
55 |
56 | let match = null;
57 | /* eslint-disable no-extra-boolean-cast */
58 | while (!!(match = PATTERN_COLOR_EXP.exec(decl.value))) {
59 | const source = decl.source;
60 | const line = source.start.line;
61 | const lineContent = getLineContent(line, source.input.css);
62 | const col = source.start.column + decl.prop.length + decl.raws.between.length + match.index;
63 | result.warn(RULENAME, {
64 | node: decl,
65 | ruleName: RULENAME,
66 | line: line,
67 | col: col,
68 | message: MSG,
69 | colorMessage: '`'
70 | + changeColorByStartAndEndIndex(
71 | lineContent, col, source.end.column
72 | )
73 | + '` '
74 | + chalk.grey(MSG)
75 | });
76 | global.CSSHINT_INVALID_ALL_COUNT++;
77 | }
78 | /* eslint-enable no-extra-boolean-cast */
79 | });
80 | }
81 | }
82 | );
83 |
--------------------------------------------------------------------------------
/src/rule/min-font-size.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file min-font-size 的检测逻辑
3 | * 037: [强制] 需要在 Windows 平台显示的中文内容,其字号应不小于 `12px`。
4 | * @author ielgnaw(wuji0223@gmail.com)
5 | */
6 |
7 | import chalk from 'chalk';
8 | import postcss from 'postcss';
9 |
10 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
11 |
12 | /**
13 | * 当前文件所代表的规则名称
14 | *
15 | * @const
16 | * @type {string}
17 | */
18 | const RULENAME = 'min-font-size';
19 |
20 | /**
21 | * 数字正则
22 | *
23 | * @const
24 | * @type {RegExp}
25 | */
26 | const PATTERN_NUMERIC = /^\d+[\.\d]*$/;
27 |
28 | /**
29 | * 错误信息
30 | *
31 | * @const
32 | * @type {string}
33 | */
34 | const MSG = 'font-size should not be less than ';
35 |
36 | /**
37 | * 具体的检测逻辑
38 | *
39 | * @param {Object} opts 参数
40 | * @param {*} opts.ruleVal 当前规则具体配置的值
41 | * @param {string} opts.fileContent 文件内容
42 | * @param {string} opts.filePath 文件路径
43 | */
44 | export const check = postcss.plugin(RULENAME, opts =>
45 | (css, result) => {
46 |
47 | if (!opts.ruleVal || isNaN(opts.ruleVal)) {
48 | return;
49 | }
50 |
51 | const msgWithVal = MSG + opts.ruleVal + 'px';
52 |
53 | css.walkDecls(decl => {
54 |
55 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
56 | return;
57 | }
58 |
59 | if (decl.prop === 'font-size') {
60 | if (parseFloat(decl.value) < opts.ruleVal) {
61 | const source = decl.source;
62 | const line = source.start.line;
63 | const lineContent = getLineContent(line, source.input.css);
64 | const val = postcss.list.split(decl.value, 'px')[0];
65 | if (PATTERN_NUMERIC.test(val)) {
66 | const col = source.start.column + decl.prop.length + decl.raws.between.length;
67 | result.warn(RULENAME, {
68 | node: decl,
69 | ruleName: RULENAME,
70 | line: line,
71 | col: col,
72 | message: msgWithVal,
73 | colorMessage: '`'
74 | + changeColorByStartAndEndIndex(
75 | lineContent, col, source.end.column
76 | )
77 | + '` '
78 | + chalk.grey(msgWithVal)
79 | });
80 | global.CSSHINT_INVALID_ALL_COUNT++;
81 | }
82 | }
83 | }
84 | });
85 | }
86 | );
87 |
--------------------------------------------------------------------------------
/src/rule/ids.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file ids 的检测逻辑
3 | * Selectors should not contain IDs
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'ids';
20 |
21 | /**
22 | * 错误的信息
23 | *
24 | * @const
25 | * @type {string}
26 | */
27 | const MSG = 'Selectors should not contain IDs';
28 |
29 | /**
30 | * 具体的检测逻辑
31 | *
32 | * @param {Object} opts 参数
33 | * @param {*} opts.ruleVal 当前规则具体配置的值
34 | * @param {string} opts.fileContent 文件内容
35 | * @param {string} opts.filePath 文件路径
36 | */
37 | export const check = postcss.plugin(RULENAME, opts =>
38 | (css, result) => {
39 |
40 | if (!opts.ruleVal) {
41 | return;
42 | }
43 |
44 | css.walkRules(rule => {
45 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
46 | return;
47 | }
48 |
49 | const {selector, source} = rule;
50 | const selectorGroup = selector.split(',');
51 | let line = source.start.line;
52 | let col = source.start.column;
53 | let lineContent = getLineContent(line, source.input.css);
54 |
55 | for (let i = 0, len = selectorGroup.length; i < len; i++) {
56 | let selectorInGroup = selectorGroup[i] || '';
57 | // 去掉 attr 选择器
58 | selectorInGroup = selectorInGroup.replace(/\[.+?\](?::[^\s>+~\.#\[]+)?/g, '');
59 | const match = selectorInGroup.match(/#[^\s>+~\.#\[]+/);
60 | if (match) {
61 | if (selectorInGroup.slice(0, 1) === '\n') {
62 | line = line + 1;
63 | lineContent = getLineContent(line, source.input.css);
64 | col = col + match.index - 1;
65 | }
66 | else {
67 | col = col + match.index;
68 | }
69 | result.warn(RULENAME, {
70 | node: rule,
71 | ruleName: RULENAME,
72 | line: line,
73 | col: col,
74 | message: MSG,
75 | colorMessage: '`'
76 | + lineContent.replace(match[0], chalk.magenta(match[0]))
77 | + '` '
78 | + chalk.grey(MSG)
79 | });
80 | }
81 | }
82 | });
83 | }
84 | );
85 |
--------------------------------------------------------------------------------
/src/rule/require-number.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file require-number 的检测逻辑
3 | * `font-weight` 对应 039: [强制] `font-weight` 属性必须使用数值方式描述。
4 | * `line-height` 对应 040: [建议] `line-height` 在定义文本段落时,应使用数值。
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent, changeColorByStartAndEndIndex} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'require-number';
20 |
21 | const PATTERN_NUMERIC = /^\d*[\.\d%]*$/;
22 |
23 | /**
24 | * 错误的信息
25 | *
26 | * @const
27 | * @type {string}
28 | */
29 | const MSG = ' must be a number value';
30 |
31 | const arrayProto = Array.prototype;
32 |
33 | /**
34 | * 具体的检测逻辑
35 | *
36 | * @param {Object} opts 参数
37 | * @param {*} opts.ruleVal 当前规则具体配置的值
38 | * @param {string} opts.fileContent 文件内容
39 | * @param {string} opts.filePath 文件路径
40 | */
41 | export const check = postcss.plugin(RULENAME, opts =>
42 | (css, result) => {
43 |
44 | const ruleVal = opts.ruleVal;
45 | const realRuleVal = [];
46 | arrayProto.push[Array.isArray(ruleVal) ? 'apply' : 'call'](realRuleVal, ruleVal);
47 |
48 | if (realRuleVal.length) {
49 |
50 | css.walkDecls(decl => {
51 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
52 | return;
53 | }
54 |
55 | const prop = decl.prop;
56 |
57 | if (realRuleVal.indexOf(prop) !== -1) {
58 | if (!PATTERN_NUMERIC.test(decl.value)) {
59 | const source = decl.source;
60 | const line = source.start.line;
61 | const lineContent = getLineContent(line, source.input.css);
62 | const col = source.start.column + decl.prop.length + decl.raws.between.length;
63 | result.warn(RULENAME, {
64 | node: decl,
65 | ruleName: RULENAME,
66 | errorChar: prop,
67 | line: line,
68 | col: col,
69 | message: prop + MSG,
70 | colorMessage: '`'
71 | + changeColorByStartAndEndIndex(
72 | lineContent, col, source.end.column
73 | )
74 | + '` '
75 | + chalk.grey(prop + MSG)
76 | });
77 | global.CSSHINT_INVALID_ALL_COUNT++;
78 | }
79 | }
80 |
81 | });
82 | }
83 | }
84 | );
85 |
--------------------------------------------------------------------------------
/src/rule/text-indent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file text-indent 的检测逻辑
3 | * Checks for text indent less than -99px
4 | * https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent
5 | * @author ielgnaw(wuji0223@gmail.com)
6 | */
7 |
8 | import chalk from 'chalk';
9 | import postcss from 'postcss';
10 |
11 | import {getLineContent, getPropertyValue} from '../util';
12 |
13 | /**
14 | * 当前文件所代表的规则名称
15 | *
16 | * @const
17 | * @type {string}
18 | */
19 | const RULENAME = 'text-indent';
20 |
21 | /**
22 | * 错误的信息
23 | *
24 | * @const
25 | * @type {string}
26 | */
27 | const MSG = ''
28 | + 'Negative text-indent doesn\'t work well with RTL.'
29 | + 'If you use text-indent for image replacement explicitly set direction for that item to ltr';
30 |
31 | let textIndentDecl;
32 | let direction;
33 |
34 | /**
35 | * 具体的检测逻辑
36 | *
37 | * @param {Object} opts 参数
38 | * @param {*} opts.ruleVal 当前规则具体配置的值
39 | * @param {string} opts.fileContent 文件内容
40 | * @param {string} opts.filePath 文件路径
41 | */
42 | export const check = postcss.plugin(RULENAME, opts =>
43 | (css, result) => {
44 | if (!opts.ruleVal) {
45 | return;
46 | }
47 |
48 | css.walkRules(rule => {
49 |
50 | textIndentDecl = false;
51 | direction = 'inherit';
52 |
53 | rule.walkDecls(decl => {
54 | if (global.CSSHINT_INVALID_ALL_COUNT >= opts.maxError) {
55 | return;
56 | }
57 | const prop = decl.prop;
58 | const value = getPropertyValue(decl.value);
59 |
60 | if (prop === 'text-indent' && value[0].value < -99) {
61 | textIndentDecl = decl;
62 | }
63 | else if (prop === 'direction' && value.value === 'ltr') {
64 | direction = 'ltr';
65 | }
66 | });
67 |
68 | if (textIndentDecl && direction !== 'ltr') {
69 | const source = textIndentDecl.source;
70 | const line = source.start.line;
71 | const lineContent = getLineContent(line, source.input.css);
72 | const col = source.start.column;
73 | result.warn(RULENAME, {
74 | node: rule,
75 | ruleName: RULENAME,
76 | line: line,
77 | col: col,
78 | message: MSG,
79 | colorMessage: '`'
80 | + lineContent.replace(
81 | textIndentDecl.prop,
82 | chalk.magenta(textIndentDecl.prop)
83 | )
84 | + '` '
85 | + chalk.grey(MSG)
86 | });
87 | global.CSSHINT_INVALID_ALL_COUNT++;
88 | }
89 | });
90 | }
91 | );
92 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | CSSHint
2 | ===
3 | [](https://travis-ci.org/ecomfe/node-csshint)
4 | [](http://badge.fury.io/js/csshint)
5 | [](https://coveralls.io/r/ecomfe/node-csshint)
6 | [](https://david-dm.org/ecomfe/node-csshint)
7 | [](https://david-dm.org/ecomfe/node-csshint#info=devDependencies)
8 |
9 | CSSHint 是一个基于 NodeJS 的代码规范审查工具,目前的规则是基于 ecomfe 的 [CSS 编码规范](https://github.com/ecomfe/spec/blob/master/css-style-guide.md),同时也覆盖了 [CSSLint](https://github.com/CSSLint/csslint) 的[规则](https://github.com/CSSLint/csslint/wiki/Rules)。
10 |
11 | 经过了一段时间的重构,终于来到这个版本。在这个版本中,`css`解析器切换成 [postcss](https://github.com/postcss/postcss)。此外,这个版本里,改变了实现方式,性能较以前的版本有比较大的提升。同时,在全局`global`对象上挂载了如下三个属性:
12 |
13 | - `global.CSSHINT_INVALID_ALL_COUNT`: 用于记录全局的`warn`个数,为`max-error`规则服务。
14 | - `global.CSSHINT_HEXCOLOR_CASE_FLAG`: 记录项目级别的颜色值的大小写信息,0: 小写, 1: 大写,为`unifying-color-case-sensitive`规则服务。
15 | - `global.CSSHINT_FONTFAMILY_CASE_FLAG`: 记录项目级别的`font-family`大小写信息,为`unifying-font-family-case-sensitive`规则服务。
16 |
17 | [配置参考](https://github.com/ecomfe/node-csshint/blob/master/lib/config.js)
18 |
19 |
20 | Install & Update
21 | -------
22 |
23 | CSSHint 已发布到 npm 上,可通过如下命令安装。
24 |
25 | $ [sudo] npm install csshint [-g]
26 |
27 | 升级 CSSHint 请用如下命令。
28 |
29 | $ [sudo] npm update csshint [-g]
30 |
31 |
32 | Usage
33 | ------
34 |
35 | - in CLI
36 |
37 | $ csshint -v // 显示版本信息
38 | $ csshint [filePath|dirPath] // 对 file 或 dir 执行 csshint
39 | - in Node.js
40 |
41 | /**
42 | * 检测 css 文件内容
43 | *
44 | * @param {string} fileContent 文件内容
45 | * @param {Object=} config 检测规则的配置,可选
46 | *
47 | * @return {Promise} Promise 对象,
48 | * Promise 对象的 reject 和 resolve 的回调函数的参数格式如下,
49 | * {
50 | * path: {string} 文件路径
51 | * messages: {Array.