├── .editorconfig
├── .fecsrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── bin
└── htmlcs
├── browser.js
├── index.js
├── lib
├── cli
│ ├── format.js
│ ├── helper.js
│ ├── hint.js
│ └── index.js
├── config.js
├── default
│ ├── htmlcsrc
│ └── rule-map.json
├── element.js
├── fs-util.js
├── htmlcs.js
├── node.js
├── other-formatter.js
├── parse.js
├── reporter.js
├── rules.js
├── rules
│ ├── asset-type.js
│ ├── attr-lowercase.js
│ ├── attr-no-duplication.js
│ ├── attr-value-double-quotes.js
│ ├── bool-attribute-value.js
│ ├── button-name.js
│ ├── button-type.js
│ ├── charset.js
│ ├── css-in-head.js
│ ├── doctype.js
│ ├── html-lang.js
│ ├── id-class-ad-disabled.js
│ ├── ie-edge.js
│ ├── img-alt.js
│ ├── img-src.js
│ ├── img-title.js
│ ├── img-width-height.js
│ ├── indent-char.js
│ ├── label-for-input.js
│ ├── lowercase-class-with-hyphen.js
│ ├── lowercase-id-with-hyphen.js
│ ├── max-len.js
│ ├── multiple-stylesheets.js
│ ├── nest.js
│ ├── new-line-for-blocks.js
│ ├── no-bom.js
│ ├── no-duplication-id-and-name.js
│ ├── no-hook-class.js
│ ├── no-meta-css.js
│ ├── no-space-before-tag-end.js
│ ├── protocol-omitted-in-href.js
│ ├── rel-stylesheet.js
│ ├── script-content.js
│ ├── script-in-tail.js
│ ├── self-close.js
│ ├── spec-char-escape.js
│ ├── style-content.js
│ ├── style-disabled.js
│ ├── tag-pair.js
│ ├── tagname-lowercase.js
│ ├── title-required.js
│ ├── unique-id.js
│ ├── unnecessary-whitespace-in-text.js
│ └── viewport.js
└── util.js
├── package.json
└── test
├── config-in-comment
├── .htmlcsrc
├── case1.html
├── case10.html
├── case11.html
├── case2.html
├── case3.html
├── case4.html
├── case5.html
├── case6.html
├── case7.html
├── case8.html
├── case9.html
└── test.spec.js
├── fixture
└── all.html
├── lib
├── config.spec.js
├── element.spec.js
├── fs-util.spec.js
├── htmlcs.spec.js
├── node.spec.js
├── other-formatter.spec.js
├── parse.spec.js
├── reporter.spec.js
├── rules.spec.js
├── rules
│ ├── asset-type
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── attr-lowercase
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── attr-no-duplication
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── attr-value-double-quotes
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── bool-attribute-value
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── button-name
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── button-type
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── charset
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ ├── case4.html
│ │ ├── case5.html
│ │ ├── case6.html
│ │ └── test.spec.js
│ ├── css-in-head
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── doctype
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ ├── case4.html
│ │ └── test.spec.js
│ ├── html-lang
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ └── test.spec.js
│ ├── id-class-ad-disabled
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ └── test.spec.js
│ ├── ie-edge
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ ├── case4.html
│ │ ├── case5.html
│ │ └── test.spec.js
│ ├── img-alt
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── img-src
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── img-title
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── img-width-height
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── indent-char
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ ├── case4.html
│ │ ├── case5.html
│ │ └── test.spec.js
│ ├── label-for-input
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── lowercase-class-with-hyphen
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── lowercase-id-with-hyphen
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── max-len
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── multiple-stylesheets
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── nest
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── new-line-for-blocks
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── no-bom
│ │ ├── .htmlcsrc
│ │ ├── case-no-bom.html
│ │ ├── case-with-bom.html
│ │ └── test.spec.js
│ ├── no-duplication-id-and-name
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── no-hook-class
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── no-meta-css
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── no-space-before-tag-end
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── protocol-omitted-in-href
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── rel-stylesheet
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── script-content
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── script-in-tail
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── self-close
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ └── test.spec.js
│ ├── spec-char-escape
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── style-content
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── style-disabled
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── tag-pair
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── tagname-lowercase
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── title-required
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ ├── case4.html
│ │ ├── case5.html
│ │ ├── case6.html
│ │ ├── case7.html
│ │ ├── case8.html
│ │ └── test.spec.js
│ ├── unique-id
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ ├── unnecessary-trailing-whitespace-in-text
│ │ ├── .htmlcsrc
│ │ ├── case.html
│ │ └── test.spec.js
│ └── viewport
│ │ ├── .htmlcsrc
│ │ ├── case1.html
│ │ ├── case2.html
│ │ ├── case3.html
│ │ ├── case4.html
│ │ └── test.spec.js
└── util.spec.js
└── max-error
├── .htmlcsrc
├── case.html
└── test.spec.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 |
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.fecsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "lib",
4 | "cli",
5 | "test/**/*.spec.js"
6 | ],
7 |
8 | "eslint": {
9 | "env": {
10 | "node": true,
11 | "browser": false
12 | },
13 |
14 | "rules": {
15 | "no-console": 0
16 | }
17 | },
18 |
19 | "csshint": {
20 |
21 | },
22 |
23 | "htmlcs": {
24 |
25 | }
26 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | .editorconfig
3 | .fecsrc
4 | .gitignore
5 | .travis.yml
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "4"
4 | - "5"
5 | - "6"
6 | after_script:
7 | - npm run coveralls
8 | sudo: false
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Baidu EFE team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bin/htmlcs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var yargs = require('yargs');
4 | var cli = require('../lib/cli');
5 |
6 | // usage, version & help info
7 | yargs = yargs
8 | .usage('Usage: $0 [options] [target...]')
9 | .version(require('../package.json').version).alias('v', 'version')
10 | .help('h').alias('h', 'help');
11 |
12 | // commands
13 | cli.commands.forEach(function (command) {
14 | yargs = yargs.command(command.name, command.describe);
15 | command.examples.forEach(function (example) {
16 | yargs = yargs.example(example[0], example[1]);
17 | });
18 | });
19 |
20 | // options
21 | cli.options.forEach(function (option) {
22 | yargs = yargs.option(option.name, option);
23 | });
24 |
25 | var argv = yargs.argv._ || [];
26 |
27 | // command name
28 | var commandName = argv[0];
29 | if (!commandName) {
30 | cli.helper.dealError('Command is required.');
31 | }
32 |
33 | // command
34 | var command = cli.commands.filter(function (item) {
35 | return item.name == commandName;
36 | })[0];
37 | if (!command) {
38 | cli.helper.dealError('Unknown command: "' + commandName + '".');
39 | }
40 |
41 | // targets
42 | var targets = argv.slice(1);
43 | if (!targets.length) {
44 | console.log('Warn: Target is not specified, use "./" as default.\n');
45 | targets.push('./');
46 | }
47 |
48 | // target files
49 | var targetFiles = cli.helper.getTargetFiles(targets);
50 | if (!targetFiles.length) {
51 | cli.helper.dealError('No matched file found.');
52 | }
53 |
54 | // execute command
55 | command.handler(yargs.argv, targetFiles);
56 |
--------------------------------------------------------------------------------
/browser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file main file for browser
3 | * @author nighca
4 | */
5 |
6 | var htmlcs = require('./lib/htmlcs');
7 |
8 | var notSupported = function () {
9 | throw new Error('Sorry, this method is not supported in browser.')
10 | };
11 |
12 | module.exports = {
13 | addRule: htmlcs.addRule,
14 |
15 | hint: htmlcs.hint,
16 | format: htmlcs.format,
17 |
18 | hintFile: notSupported,
19 | formatFile: notSupported
20 | };
21 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file main file
3 | * @author nighca
4 | */
5 |
6 | var fs = require('fs');
7 |
8 | var config = require('./lib/config');
9 | var htmlcs = require('./lib/htmlcs');
10 |
11 | /**
12 | * Do hint with given filePath & option for readFile.
13 | *
14 | * @param {string} filePath - path of the target file
15 | * @param {Object=} options - option for readFile
16 | * @return {Report[]} the hint result, list of reports
17 | */
18 | var hintFile = function (filePath, options) {
19 | options = options || {
20 | encoding: 'utf-8'
21 | };
22 |
23 | var cnt = fs.readFileSync(filePath, options);
24 | var cfg = config.load(filePath);
25 |
26 | return htmlcs.hint(cnt, cfg);
27 | };
28 |
29 | /**
30 | * Do format with given filePath & option for readFile
31 | *
32 | * @param {string} filePath - path of the target file
33 | * @param {Object=} options - option for readFile
34 | * @return {string} the formatted code
35 | */
36 | var formatFile = function (filePath, options) {
37 | options = options || {
38 | encoding: 'utf-8'
39 | };
40 |
41 | var cnt = fs.readFileSync(filePath, options);
42 | var cfg = config.load(filePath);
43 |
44 | return htmlcs.format(cnt, cfg);
45 | };
46 |
47 | module.exports = {
48 | addRule: htmlcs.addRule,
49 |
50 | hint: htmlcs.hint,
51 | hintAsync: htmlcs.hintAsync,
52 | format: htmlcs.format,
53 | formatAsync: htmlcs.formatAsync,
54 |
55 | hintFile: hintFile,
56 | formatFile: formatFile
57 | };
58 |
--------------------------------------------------------------------------------
/lib/cli/format.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file command: format
3 | * @author nighca
4 | */
5 |
6 | var fs = require('fs');
7 | var differ = require('differ-cli/lib/differ');
8 | var helper = require('./helper');
9 | var htmlcs = require('../../');
10 |
11 | module.exports = {
12 | name: 'format',
13 | describe: 'Do format given file(s)',
14 | examples: [
15 | ['$0 format foo.html', 'do format foo.html'],
16 | ['$0 format --diff foo.html', 'do format foo.html & show diff result'],
17 | ['$0 format --in-place foo.html', 'do format foo.html & write file in place']
18 | ],
19 |
20 | handler: function (options, targetFiles) {
21 | // format directly
22 | var format = htmlcs.formatFile;
23 |
24 | // specified config
25 | if (options.config) {
26 | var config = helper.loadSpecifiedConfig(options.config);
27 | // format with specified config
28 | format = function (filePath) {
29 | return htmlcs.format(helper.readFile(filePath), config);
30 | };
31 | }
32 |
33 | targetFiles.forEach(function (filePath) {
34 | var result = format(filePath);
35 |
36 | if (options.diff) {
37 | console.log(
38 | filePath
39 | + ':'
40 | + differ(helper.readFile(filePath), result)
41 | );
42 | return;
43 | }
44 |
45 | if (options['in-place']) {
46 | fs.writeFileSync(filePath, result);
47 | console.log('√', filePath);
48 | return;
49 | }
50 |
51 | console.log(result);
52 | });
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/lib/cli/helper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file helpers for cli operation
3 | * @author nighca
4 | */
5 |
6 | var fs = require('fs');
7 | var path = require('path');
8 | var walk = require('walk');
9 | var yargs = require('yargs');
10 | var config = require('../config');
11 |
12 | /**
13 | * Deal with error & exit.
14 | *
15 | * @param {string} msg - error message
16 | */
17 | var dealError = function (msg) {
18 | console.log('Error: ' + msg + '\n');
19 | yargs.showHelp();
20 | process.exit(1);
21 | };
22 |
23 | /**
24 | * Read file.
25 | *
26 | * @param {string} filePath - path of given file
27 | * @return {string} file content
28 | */
29 | var readFile = function (filePath) {
30 | return fs.readFileSync(filePath, {encoding: 'utf-8'});
31 | };
32 |
33 | /**
34 | * Load content of specified config file.
35 | *
36 | * @param {string} configFilePath - path of specified config file
37 | * @return {?Object} the config content
38 | */
39 | var loadSpecifiedConfig = function (configFilePath) {
40 | try {
41 | return config.parse(readFile(configFilePath));
42 | }
43 | catch (e) {
44 | dealError('Load config (' + configFilePath + ') failed: ' + e.message);
45 | }
46 | };
47 |
48 | var HTML_EXT_PATTERN = /\.html?$/;
49 |
50 | /**
51 | * Get target files with given targets (file / directory path).
52 | *
53 | * @param {Array} targets - list of given targets
54 | * @return {Array} list of target files' path
55 | */
56 | var getTargetFiles = function (targets) {
57 | return targets.reduce(function (files, target) {
58 | var stat = fs.statSync(target);
59 |
60 | if (stat.isFile()) {
61 | files.push(target);
62 | return files;
63 | }
64 |
65 | if (stat.isDirectory()) {
66 | walk.walkSync(target, {
67 | followLinks: false,
68 | filters: ['node_modules', 'bower_components', 'Temp', '_Temp'],
69 | listeners: {
70 | file: function (root, fileStat, next) {
71 | var filePath = path.join(root, fileStat.name);
72 |
73 | // filter with suffix (.html)
74 | if (HTML_EXT_PATTERN.test(filePath)) {
75 | files.push(filePath);
76 | }
77 | next();
78 | }
79 | }
80 | });
81 | return files;
82 | }
83 | }, []);
84 | };
85 |
86 | module.exports = {
87 | dealError: dealError,
88 | readFile: readFile,
89 | loadSpecifiedConfig: loadSpecifiedConfig,
90 | getTargetFiles: getTargetFiles
91 | };
92 |
--------------------------------------------------------------------------------
/lib/cli/hint.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file command: hint
3 | * @author nighca
4 | */
5 |
6 | var helper = require('./helper');
7 | var htmlcs = require('../../');
8 |
9 | module.exports = {
10 | name: 'hint',
11 | describe: 'Do hint given file(s)',
12 | examples: [
13 | ['$0 hint foo.html', 'do hint foo.html'],
14 | ['$0 hint foo.html bar.html', 'do hint foo.html & bar.html'],
15 | ['$0 hint ./', 'do hint html files under ./']
16 | ],
17 |
18 | handler: function (options, targetFiles) {
19 | // hint directly
20 | var hint = htmlcs.hintFile;
21 |
22 | // specified config
23 | if (options.config) {
24 | var config = helper.loadSpecifiedConfig(options.config);
25 | // hint with specified config
26 | hint = function (filePath) {
27 | return htmlcs.hint(helper.readFile(filePath), config);
28 | };
29 | }
30 |
31 | var hasError = false;
32 |
33 | targetFiles.forEach(function (filePath) {
34 | var result = hint(filePath);
35 |
36 | console.log(filePath + ':');
37 |
38 | if (result.length) {
39 | hasError = true;
40 |
41 | result.forEach(function (item) {
42 | console.log(
43 | '[%s] line %d, column %d: %s (%s, %s)',
44 | item.type,
45 | item.line,
46 | item.column,
47 | item.message,
48 | item.rule,
49 | item.code
50 | );
51 | });
52 | }
53 | else {
54 | console.log('No hint result.');
55 | }
56 |
57 | console.log('');
58 | });
59 |
60 | if (hasError) {
61 | process.exit(1);
62 | }
63 | }
64 | };
65 |
--------------------------------------------------------------------------------
/lib/cli/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file cli methods
3 | * @author nighca
4 | */
5 |
6 | module.exports = {
7 |
8 | commands: [
9 | require('./hint'),
10 | require('./format')
11 | ],
12 |
13 | options: [
14 | {
15 | name: 'c',
16 | alias: 'config',
17 | describe: 'Path to custom configuration file.',
18 | type: 'string'
19 | },
20 | {
21 | name: 'diff',
22 | describe: 'Check code style and output char diff.',
23 | type: 'boolean'
24 | },
25 | {
26 | name: 'i',
27 | alias: 'in-place',
28 | describe: 'Edit input files in place; use with care!',
29 | type: 'boolean'
30 | }
31 | ],
32 |
33 | helper: require('./helper')
34 | };
35 |
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file get config
3 | * @author nighca
4 | */
5 |
6 | var path = require('path');
7 | var Manis = require('manis');
8 |
9 | var util = require('./util');
10 | var fsUtil = require('./fs-util');
11 |
12 | /**
13 | * Name of the config file.
14 | *
15 | * @type {string}
16 | * @const
17 | */
18 | var CONFIG_FILENAME = '.htmlcsrc';
19 |
20 | /**
21 | * Parse given config text content.
22 | *
23 | * @param {string} text - given config text content
24 | * @return {?Object} the config content
25 | */
26 | var parseConfig = function (text) {
27 | return Manis.loader(text, '');
28 | };
29 |
30 | /**
31 | * Load config for given file.
32 | *
33 | * @param {string} filePath - path of given file
34 | * @param {boolean=} refresh - if skips cache
35 | * @return {?Object} the config content
36 | */
37 | var loadConfig = util.cachable(function (filePath, refresh) {
38 | var options = {
39 | orphan: true
40 | };
41 |
42 | var manis = new Manis(CONFIG_FILENAME, options);
43 | manis.setDefault(path.join(fsUtil.app.root, 'lib/default/htmlcsrc'), options);
44 | manis.setUserConfig();
45 |
46 | return manis.from(filePath);
47 | });
48 |
49 | module.exports = {
50 | fileName: CONFIG_FILENAME,
51 | parse: parseConfig,
52 | load: loadConfig
53 | };
54 |
--------------------------------------------------------------------------------
/lib/default/rule-map.json:
--------------------------------------------------------------------------------
1 | {
2 | "asset-type": "001,002",
3 |
4 | "attr-lowercase": "029",
5 |
6 | "attr-no-duplication": "030",
7 |
8 | "attr-value-double-quotes": "028",
9 |
10 | "bool-attribute-value": "003",
11 |
12 | "button-name": "004",
13 |
14 | "button-type": "005",
15 |
16 | "charset": "006,007",
17 |
18 | "css-in-head": "008",
19 |
20 | "doctype": "009,041",
21 |
22 | "html-lang": "010",
23 |
24 | "id-class-ad-disabled": "031",
25 |
26 | "ie-edge": "011",
27 |
28 | "img-alt": "012",
29 |
30 | "img-src": "013",
31 |
32 | "img-title": "014",
33 |
34 | "img-width-height": "015,016,017",
35 |
36 | "lowercase-class-with-hyphen": "018,019",
37 |
38 | "lowercase-id-with-hyphen": "020,021",
39 |
40 | "nest": "041,042",
41 |
42 | "rel-stylesheet": "022",
43 |
44 | "script-content": "037",
45 |
46 | "script-in-tail": "023",
47 |
48 | "indent-char": "032",
49 |
50 | "self-close": "039,040",
51 |
52 | "spec-char-escape": "033",
53 |
54 | "style-content": "038",
55 |
56 | "style-disabled": "034",
57 |
58 | "tag-pair": "035",
59 |
60 | "tagname-lowercase": "036",
61 |
62 | "title-required": "024,025",
63 |
64 | "unique-id": "026",
65 |
66 | "no-duplication-id-and-name": "043",
67 |
68 | "viewport": "027",
69 |
70 | "label-for-input": "044",
71 |
72 | "no-meta-css": "045",
73 |
74 | "no-hook-class": "047",
75 |
76 | "max-len": "048",
77 |
78 | "no-bom": "046"
79 | }
80 |
--------------------------------------------------------------------------------
/lib/fs-util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file fs-relative util methods
3 | * @author nighca
4 | */
5 |
6 | var fs = require('fs');
7 | var path = require('path');
8 |
9 | /**
10 | * Application (htmlcs) info.
11 | *
12 | * @type {Object}
13 | * @property {string} root - root path of application (htmlcs) code
14 | */
15 | var app = {
16 | root: path.resolve(__dirname, '../')
17 | };
18 |
19 |
20 | /**
21 | * Get path of home(~).
22 | *
23 | * @return {string} path of home
24 | */
25 | var getHomePath = function () {
26 | var homePath = '';
27 | var environment = process.env;
28 | var paths = [
29 | environment.USERPROFILE,
30 | environment.HOME,
31 | environment.HOMEPATH,
32 | environment.HOMEDRIVE + environment.HOMEPATH
33 | ];
34 |
35 | while (paths.length) {
36 | homePath = paths.shift();
37 | if (fs.existsSync(homePath)) {
38 | return homePath;
39 | }
40 | }
41 | };
42 |
43 | module.exports = {
44 | app: app,
45 | getHomePath: getHomePath
46 | };
47 |
--------------------------------------------------------------------------------
/lib/other-formatter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file formatters for non-html content (js, css)
3 | * @author nighca
4 | */
5 |
6 | var jformatter = require('jformatter');
7 | var cssbeautify = require('cssbeautify');
8 |
9 | /**
10 | * 自定义formatter方法格式
11 | *
12 | * formatter
13 | * @param {string} content 标签内部的内容
14 | * @param {Node} node 标签节点
15 | * @param {Object} opt 当前format配置
16 | * @param {Object} helper 辅助方法(具体格式见下)
17 | * @return {string} format后的结果
18 | *
19 | * helper
20 | * @property {function} indent 为内容添加缩进,具体长度取决于当前节点层级及format配置(具体格式见下)
21 | * @property {function} trim 移除内容首尾的空行(无内容或仅含\s\t)(具体格式见下)
22 | *
23 | * indent
24 | * @param {string} content 原始内容
25 | * @return {string} 添加缩进后的结果
26 | *
27 | * trim
28 | * @param {string} content 原始内容
29 | * @return {string} 移除内容首尾空行后的结果
30 | */
31 |
32 | module.exports = {
33 |
34 | script: function (content, node, opt, helper) {
35 | var type = node.getAttribute('type');
36 |
37 | // javascript content
38 | if (!type || type === 'text/javascript') {
39 | var formatted = jformatter.format(content);
40 |
41 | // add indent
42 | content = helper.indent(formatted);
43 | }
44 |
45 | return helper.trim(content);
46 | },
47 |
48 | style: function (content, node, opt, helper) {
49 | var formatted = cssbeautify(content);
50 |
51 | // add indent
52 | content = helper.indent(formatted);
53 |
54 | return helper.trim(content);
55 | }
56 |
57 | };
58 |
--------------------------------------------------------------------------------
/lib/parse.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file parse code
3 | * @author nighca
4 | */
5 |
6 | var htmlparser2 = require('htmlparser2');
7 | var Parser = htmlparser2.Parser;
8 | var DomHandler = htmlparser2.DomHandler;
9 |
10 | var util = require('./util');
11 | var Node = require('./node');
12 |
13 | /**
14 | * Transform node to Node instance & recursively transform its children.
15 | *
16 | * @param {Object} node - given node
17 | * @return {Node} result node
18 | */
19 | var transformRecursively = function (node) {
20 | Node.init(node);
21 |
22 | node.childNodes.forEach(function (childNode) {
23 | transformRecursively(childNode);
24 | });
25 |
26 | return node;
27 | };
28 |
29 | /**
30 | * Wrap node list with .
31 | *
32 | * @param {Array} arr - node list
33 | * @return {Node} document node
34 | */
35 | var wrapDocument = function (arr) {
36 | var document = htmlparser2.parseDOM('')[0];
37 |
38 | document.children = arr;
39 |
40 | for (var i = 0; i < arr.length; i++) {
41 | var node = arr[i];
42 |
43 | node.prev = arr[i - 1] || null;
44 | node.next = arr[i + 1] || null;
45 |
46 | node.root = document;
47 | node.parent = null;
48 | }
49 |
50 | transformRecursively(document);
51 |
52 | // fix startIndex missing, cause is parsed seperately
53 | document.startIndex = document.documentElement && document.documentElement.startIndex | 0;
54 |
55 | return document;
56 | };
57 |
58 | /**
59 | * Get a HTML parser.
60 | *
61 | * @param {Object} options - options for create parser
62 | * @return {Parser} HTML parser
63 | */
64 | var getParser = function (options) {
65 | // merge with default options
66 | options = util.extend({
67 | lowerCaseAttributeNames: false,
68 | recognizeCDATA: true
69 | }, options);
70 |
71 | // init handler
72 | var handler = new DomHandler({
73 | withStartIndices: true
74 | });
75 |
76 | // init parser
77 | var parser = new Parser(handler, options);
78 |
79 | // make handler accessible
80 | parser.handler = handler;
81 |
82 | // make tokenizer emittable & accessible
83 | parser.tokenizer = util.emittable(parser._tokenizer, [
84 | 'attribdata',
85 | 'opentagname',
86 | 'opentagend',
87 | 'selfclosingtag',
88 | 'attribname',
89 | 'attribend',
90 | 'closetag',
91 | 'declaration',
92 | 'processinginstruction',
93 | 'comment',
94 | 'cdata',
95 | 'text',
96 | 'error',
97 | 'end'
98 | ]);
99 |
100 | // make parser emittable
101 | parser = util.emittable(parser, [
102 | 'processinginstruction',
103 | 'comment',
104 | 'commentend',
105 | 'cdatastart',
106 | 'text',
107 | 'cdataend',
108 | 'error',
109 | 'closetag',
110 | 'end',
111 | 'reset',
112 | 'parserinit',
113 | 'opentagname',
114 | 'opentag',
115 | 'attribute'
116 | ]);
117 |
118 | return parser;
119 | };
120 |
121 | /**
122 | * Parse given html content to document node.
123 | *
124 | * @param {string} htmlCode - HTML code content
125 | * @param {Parser=} parser - given parser
126 | * @return {Node} document node
127 | */
128 | var parse = function (htmlCode, parser) {
129 | // get parser
130 | parser = parser || getParser();
131 |
132 | // replace "\r\n" with "\n"
133 | htmlCode = htmlCode.replace(/\r\n/g, '\n');
134 |
135 | // do parse
136 | parser.end(htmlCode);
137 |
138 | // get dom & wrap it with
139 | var document = wrapDocument(parser.handler.dom);
140 |
141 | return document;
142 | };
143 |
144 | parse.getParser = getParser;
145 |
146 | module.exports = parse;
147 |
--------------------------------------------------------------------------------
/lib/rules/asset-type.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file rule: asset-type
3 | * @author nighca
4 | */
5 |
6 | module.exports = {
7 |
8 | name: 'asset-type',
9 |
10 | desc: 'Default value of attribute "type" (/
15 |
16 |
19 |
22 |
23 |
24 |
25 |
26 | i'm a p.
27 |
28 | i'm a div.
29 |
30 |
31 |
32 |
33 | there's a div in me.
34 | i'm a div in span.
35 |
36 |
37 |
38 |
39 |
40 | i'm another div.
41 |
42 |
43 |
44 |
45 |