fdlint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +8 -0
- data/Gemfile.lock +14 -0
- data/README.md +68 -0
- data/Rakefile +92 -0
- data/bin/fdlint +17 -0
- data/lib/base_parser.rb +143 -0
- data/lib/cmd_runner.rb +145 -0
- data/lib/context.rb +31 -0
- data/lib/css/parser.rb +186 -0
- data/lib/css/reader.rb +30 -0
- data/lib/css/rule/check_compression_rule.rb +48 -0
- data/lib/css/rule/checklist.rb +45 -0
- data/lib/css/struct.rb +111 -0
- data/lib/encoding_error.rb +6 -0
- data/lib/file_validator.rb +38 -0
- data/lib/helper/code_type.rb +50 -0
- data/lib/helper/color_string.rb +44 -0
- data/lib/helper/file_reader.rb +22 -0
- data/lib/helper/strenc.rb +65 -0
- data/lib/html/parser.rb +212 -0
- data/lib/html/query.rb +96 -0
- data/lib/html/rule/check_tag_rule.rb +80 -0
- data/lib/html/struct.rb +291 -0
- data/lib/js/expr/expr.rb +66 -0
- data/lib/js/expr/left_hand.rb +63 -0
- data/lib/js/expr/operate.rb +92 -0
- data/lib/js/expr/primary.rb +166 -0
- data/lib/js/parser.rb +116 -0
- data/lib/js/rule/all.rb +35 -0
- data/lib/js/rule/checklist.rb +41 -0
- data/lib/js/rule/file_checker.rb +42 -0
- data/lib/js/rule/helper.rb +96 -0
- data/lib/js/rule/no_global.rb +87 -0
- data/lib/js/stat/if.rb +25 -0
- data/lib/js/stat/iter.rb +85 -0
- data/lib/js/stat/stat.rb +117 -0
- data/lib/js/stat/switch.rb +65 -0
- data/lib/js/stat/try.rb +28 -0
- data/lib/js/stat/var.rb +40 -0
- data/lib/js/struct.rb +248 -0
- data/lib/log_entry.rb +49 -0
- data/lib/node.rb +28 -0
- data/lib/parse_error.rb +13 -0
- data/lib/parser_visitable.rb +138 -0
- data/lib/position_info.rb +46 -0
- data/lib/printer/base_printer.rb +24 -0
- data/lib/printer/console_printer.rb +66 -0
- data/lib/printer/nocolor_printer.rb +27 -0
- data/lib/printer/vim_printer.rb +19 -0
- data/lib/rule.rb +241 -0
- data/lib/rule_helper.rb +14 -0
- data/lib/runner.rb +225 -0
- data/rules.d/css.rule +127 -0
- data/rules.d/html.dtd.rule +22 -0
- data/rules.d/html.prop.rule +51 -0
- data/rules.d/html.tag.rule +136 -0
- data/rules.d/js.file.rule +13 -0
- data/rules.d/js.jquery.rule +56 -0
- data/rules.d/js.mergefile.rule +71 -0
- data/rules.d/js.rule +84 -0
- data/test/all_tests.rb +84 -0
- data/test/cli/cli_test.rb +70 -0
- data/test/cli/log_level_test.rb +51 -0
- data/test/cli/output_format_test.rb +47 -0
- data/test/cli/type_test.rb +77 -0
- data/test/css/mac_line_end_support_test.rb +38 -0
- data/test/css/parser_test.rb +276 -0
- data/test/css/rule/check_encoding_test.rb +66 -0
- data/test/css/rule/check_list_rule_test.rb +167 -0
- data/test/css/rule/compression_test.rb +53 -0
- data/test/css/rule/file_name_test.rb +76 -0
- data/test/fixtures/css/broken.css +4 -0
- data/test/fixtures/css/cbu/36.css +52 -0
- data/test/fixtures/css/cbu/china_top.css +324 -0
- data/test/fixtures/css/cbu/default-merge.css +3 -0
- data/test/fixtures/css/cbu/default.css +13 -0
- data/test/fixtures/css/cbu/diy-merge.css +25 -0
- data/test/fixtures/css/cbu/fns-v1.css +27 -0
- data/test/fixtures/css/cbu/index_v0.1.css +12 -0
- data/test/fixtures/css/cbu/merge.css +11 -0
- data/test/fixtures/css/cbu/min.css +2 -0
- data/test/fixtures/css/cbu/my_home_admin.css +126 -0
- data/test/fixtures/css/cbu/nav.css +95 -0
- data/test/fixtures/css/cbu/pic_list.css +386 -0
- data/test/fixtures/css/cbu/quote-edit.css +18 -0
- data/test/fixtures/css/cbu/selloffer.shopwindow.css +1 -0
- data/test/fixtures/css/cbu/v1.css +9 -0
- data/test/fixtures/css/css3.css +30 -0
- data/test/fixtures/css/empty-min.css +0 -0
- data/test/fixtures/css/empty.css +0 -0
- data/test/fixtures/css/font-family.css +4 -0
- data/test/fixtures/css/gb-good.css +14 -0
- data/test/fixtures/css/gb_using_star.css +4 -0
- data/test/fixtures/css/import.css +18 -0
- data/test/fixtures/css/mac-line-sep-err-min.css +1 -0
- data/test/fixtures/css/mac-line-sep-err.css +1 -0
- data/test/fixtures/css/mac-line-sep-good-min.css +1 -0
- data/test/fixtures/css/mac-line-sep-good.css +1 -0
- data/test/fixtures/css/multi-encoding-in-a-file.css +0 -0
- data/test/fixtures/css/simple.css +1 -0
- data/test/fixtures/css/using_expr.css +8 -0
- data/test/fixtures/css/using_hack.css +21 -0
- data/test/fixtures/css/using_id.css +1 -0
- data/test/fixtures/css/using_star.css +4 -0
- data/test/fixtures/css/utf8_good.css +6 -0
- data/test/fixtures/css/utf8_good_declaring_charset.css +7 -0
- data/test/fixtures/css/utf8_using_star.css +5 -0
- data/test/fixtures/html/1-1.html +120 -0
- data/test/fixtures/html/1-2.html +120 -0
- data/test/fixtures/html/cms.html +373 -0
- data/test/fixtures/html/css_out_of_head.html +9 -0
- data/test/fixtures/html/fdev-template.html +22 -0
- data/test/fixtures/html/google.com.html +33 -0
- data/test/fixtures/html/mixed_log_levels.html +4 -0
- data/test/fixtures/html/mixed_types.html +13 -0
- data/test/fixtures/html/no_dtd.html +6 -0
- data/test/fixtures/html/readme.html +94 -0
- data/test/fixtures/html/review.board.html +163 -0
- data/test/fixtures/html/syntax_err.html +3 -0
- data/test/fixtures/html/train/detail/345/233/276/346/226/207/347/273/223/345/220/210.html +208 -0
- data/test/fixtures/html/train/detail/347/232/204Flash.html +212 -0
- data/test/fixtures/html/train/detail/347/232/204Vedio.html +212 -0
- data/test/fixtures/html/train/index.html +37 -0
- data/test/fixtures/html/train/test.html +1 -0
- data/test/fixtures/html/train//344/277/256/346/224/271/344/270/200/347/272/247/345/210/206/347/261/273.html +112 -0
- data/test/fixtures/html/train//344/277/256/346/224/271/345/255/220/345/210/206/347/261/273.html +108 -0
- data/test/fixtures/html/train//344/277/256/346/224/271/350/257/276/347/250/213.html +195 -0
- data/test/fixtures/html/train//345/215/232/345/256/242/350/256/276/347/275/256.html +142 -0
- data/test/fixtures/html/train//346/265/217/350/247/210/350/256/260/345/275/225.html +191 -0
- data/test/fixtures/html/train//346/267/273/345/212/240/344/270/200/347/272/247/345/210/206/347/261/273.html +113 -0
- data/test/fixtures/html/train//346/267/273/345/212/240/345/255/220/345/210/206/347/261/273.html +112 -0
- data/test/fixtures/html/train//346/267/273/345/212/240/350/257/276/347/250/213.html +195 -0
- data/test/fixtures/html/train//347/231/273/345/275/225.html +20 -0
- data/test/fixtures/html/train//347/256/241/347/220/206/345/210/206/347/261/273.html +210 -0
- data/test/fixtures/html/train//347/256/241/347/220/206/345/217/215/351/246/210.html +222 -0
- data/test/fixtures/html/train//347/256/241/347/220/206/350/257/276/347/250/213.html +284 -0
- data/test/fixtures/html/train//347/256/241/347/220/206/350/264/246/346/210/267.html +107 -0
- data/test/fixtures/html/train//347/275/221/344/270/212/345/237/271/350/256/255home/351/241/265.html +354 -0
- data/test/fixtures/html/train//347/275/221/345/225/206/345/237/271/350/256/255list/351/241/265.html +255 -0
- data/test/fixtures/html/train//350/256/276/347/275/256/351/246/226/351/241/265/346/216/250/350/215/220.html +168 -0
- data/test/fixtures/html/train//350/257/264/346/230/216.txt +3 -0
- data/test/fixtures/html/train//351/246/226/351/241/265/345/271/277/345/221/212/350/256/276/347/275/256.html +297 -0
- data/test/fixtures/html/unescaped.html +2 -0
- data/test/fixtures/html/view.vm +916 -0
- data/test/fixtures/js/jquery-1.7.js +9300 -0
- data/test/fixtures/js/scope-test.js +22 -0
- data/test/helper.rb +41 -0
- data/test/html/mixed_type_test.rb +35 -0
- data/test/html/parser/parse_comment_test.rb +47 -0
- data/test/html/parser/parse_dtd_test.rb +46 -0
- data/test/html/parser/parse_script_tag_test.rb +55 -0
- data/test/html/parser/parse_with_auto_close_tag_test.rb +41 -0
- data/test/html/parser/parse_with_diff_case_test.rb +38 -0
- data/test/html/parser/parse_with_emtpy_test.rb +22 -0
- data/test/html/parser/parse_with_multi_children_test.rb +27 -0
- data/test/html/parser/parse_with_multi_line_test.rb +41 -0
- data/test/html/parser/parse_with_prop_test.rb +88 -0
- data/test/html/parser/parse_with_script_tag_test.rb +26 -0
- data/test/html/parser/parse_with_selfclosing_test.rb +39 -0
- data/test/html/parser/parse_with_simple_tag_test.rb +44 -0
- data/test/html/parser/parse_with_simple_tree_test.rb +40 -0
- data/test/html/parser/parse_with_style_tag_test.rb +22 -0
- data/test/html/parser/parse_with_text_test.rb +45 -0
- data/test/html/parser_test.rb +52 -0
- data/test/html/query_test.rb +52 -0
- data/test/html/rule/check_block_level_element_test.rb +52 -0
- data/test/html/rule/check_button_test.rb +45 -0
- data/test/html/rule/check_class_count_test.rb +36 -0
- data/test/html/rule/check_css_in_head_test.rb +53 -0
- data/test/html/rule/check_dtd_test.rb +46 -0
- data/test/html/rule/check_form_element_name_test.rb +49 -0
- data/test/html/rule/check_head_contain_meta_and_title_test.rb +52 -0
- data/test/html/rule/check_html_template_test.rb +103 -0
- data/test/html/rule/check_hyperlink_with_target_test.rb +40 -0
- data/test/html/rule/check_hyperlink_with_title_test.rb +43 -0
- data/test/html/rule/check_id_n_class_downcase_test.rb +40 -0
- data/test/html/rule/check_img_with_alt_prop_test.rb +33 -0
- data/test/html/rule/check_no_import_css_test.rb +36 -0
- data/test/html/rule/check_prop_have_value_test.rb +32 -0
- data/test/html/rule/check_prop_seperator_test.rb +32 -0
- data/test/html/rule/check_style_prop_test.rb +30 -0
- data/test/html/rule/check_tag_closed_test.rb +59 -0
- data/test/html/rule/check_tag_downcase_test.rb +51 -0
- data/test/html/rule/check_unescape_char_test.rb +35 -0
- data/test/html/rule/check_unique_import_test.rb +56 -0
- data/test/html/rule_test.rb +62 -0
- data/test/js/expr/expr.rb +57 -0
- data/test/js/expr/left_hand.rb +25 -0
- data/test/js/expr/operate.rb +145 -0
- data/test/js/expr/primary.rb +89 -0
- data/test/js/parser_test.rb +98 -0
- data/test/js/rule/alert_check_test.rb +37 -0
- data/test/js/rule/all_test.rb +23 -0
- data/test/js/rule/base_test.rb +34 -0
- data/test/js/rule/file_checker_test.rb +131 -0
- data/test/js/rule/jq_check_test.rb +90 -0
- data/test/js/rule/nest_try_catch_test.rb +71 -0
- data/test/js/rule/new_object_and_new_array_test.rb +38 -0
- data/test/js/rule/no_eval_test.rb +34 -0
- data/test/js/rule/no_global_test.rb +88 -0
- data/test/js/rule/private_method_check_test.rb +58 -0
- data/test/js/rule/semicolon_test.rb +63 -0
- data/test/js/rule/stat_if_with_brace_test.rb +68 -0
- data/test/js/rule/stat_if_with_muti_else_test.rb +68 -0
- data/test/js/rule/use_strict_equal_test.rb +44 -0
- data/test/js/rule_test.rb +47 -0
- data/test/js/stat/if.rb +26 -0
- data/test/js/stat/iter.rb +115 -0
- data/test/js/stat/stat.rb +91 -0
- data/test/js/stat/switch.rb +37 -0
- data/test/js/stat/try.rb +32 -0
- data/test/js/stat/var.rb +38 -0
- data/test/parser_visitable_test.rb +102 -0
- data/test/position_info_test.rb +66 -0
- data/test/rule_dsl/dsl_basic_test.rb +91 -0
- data/test/rule_dsl/importing_test.rb +48 -0
- data/test/runner/log_level_test.rb +58 -0
- metadata +317 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
fdlint -- 让前端code review更轻松
|
|
2
|
+
=================================
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
fdlint (开发代号xray) 是根据阿里巴巴前端开发checklist开发的自动代码扫描工具。
|
|
7
|
+
可以扫描出前端程序中不符合开发规范的地方。
|
|
8
|
+
|
|
9
|
+
[](http://travis-ci.org/qhwa/fdlint)
|
|
10
|
+
|
|
11
|
+
## 语言支持
|
|
12
|
+
* html
|
|
13
|
+
* css
|
|
14
|
+
* javascript
|
|
15
|
+
|
|
16
|
+
## 使用方式
|
|
17
|
+
|
|
18
|
+
### 编辑器插件
|
|
19
|
+
|
|
20
|
+
* [Nodepad++ 插件](https://github.com/ThinkBest/fdlint-notepad-plusplus)
|
|
21
|
+
* [Vim 插件](https://github.com/qhwa/fdlint-vim)
|
|
22
|
+
|
|
23
|
+
### 命令行工具
|
|
24
|
+
|
|
25
|
+
#### ruby脚本
|
|
26
|
+
适合安装了Ruby1.9+ 环境的Windows/\*nix系统
|
|
27
|
+
|
|
28
|
+
运行方式
|
|
29
|
+
|
|
30
|
+
/path/to/fdlint [参数] <目标文件或目录>
|
|
31
|
+
|
|
32
|
+
或者使用管道:
|
|
33
|
+
|
|
34
|
+
echo '* {}' | /path/to/fdlint
|
|
35
|
+
|
|
36
|
+
参数列表:
|
|
37
|
+
|
|
38
|
+
~~~
|
|
39
|
+
Usage: fdlint
|
|
40
|
+
--css 在扫描模式下仅检查CSS文件,在管道模式下指定内容为CSS
|
|
41
|
+
--js 在扫描模式下仅检查JS文件,在管道模式下指定内容为JS
|
|
42
|
+
--html 在扫描模式下仅检查HTML文件,在管道模式下指定内容为HTML
|
|
43
|
+
-c, --charset set 指定文件默认的编码(本参数已废弃,目前自动判断字符集)
|
|
44
|
+
-d, --debug 输出调试信息
|
|
45
|
+
-l, --list 无彩色输出,等同于 '--format=nocolor'
|
|
46
|
+
-m, --checkmin 检查压缩后的js或css文件。如不指定改选项,会跳过*-min.css或*-min.js文件。
|
|
47
|
+
(e.g. *-min.js; *-min.css)
|
|
48
|
+
--format [type] 输出模式:console (默认)、nocolor 或 vim
|
|
49
|
+
--level [log_level] 输出时消息的过滤等级:warn(默认)、error 或 fatal
|
|
50
|
+
~~~
|
|
51
|
+
|
|
52
|
+
### Web
|
|
53
|
+
|
|
54
|
+
[fdlint-host](https://github.com/qhwa/fdlint-host)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## 源代码
|
|
58
|
+
|
|
59
|
+
git clone git://github.com/qhwa/fdlint.git
|
|
60
|
+
|
|
61
|
+
## 开源协议
|
|
62
|
+
|
|
63
|
+
[BSD协议](http://www.linfo.org/bsdlicense.html)
|
|
64
|
+
|
|
65
|
+
## 附录
|
|
66
|
+
|
|
67
|
+
[扫描规则](https://github.com/qhwa/fdlint/wiki/fdlint-%E6%89%AB%E6%8F%8F%E8%A7%84%E5%88%99)
|
|
68
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
task :default => [:test]
|
|
2
|
+
|
|
3
|
+
desc "run unit tests"
|
|
4
|
+
task :test do
|
|
5
|
+
require_relative 'test/all_tests'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require "rubygems"
|
|
10
|
+
require "rubygems/package_task"
|
|
11
|
+
require "rdoc/task"
|
|
12
|
+
|
|
13
|
+
require "rake/testtask"
|
|
14
|
+
Rake::TestTask.new do |t|
|
|
15
|
+
t.libs << "test"
|
|
16
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
17
|
+
t.verbose = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
task :default => ["test"]
|
|
22
|
+
|
|
23
|
+
# This builds the actual gem. For details of what all these options
|
|
24
|
+
# mean, and other ones you can add, check the documentation here:
|
|
25
|
+
#
|
|
26
|
+
# http://rubygems.org/read/chapter/20
|
|
27
|
+
#
|
|
28
|
+
spec = Gem::Specification.new do |s|
|
|
29
|
+
|
|
30
|
+
# Change these as appropriate
|
|
31
|
+
s.name = "fdlint"
|
|
32
|
+
s.version = "0.1.0"
|
|
33
|
+
s.summary = "Code reviewer for web developing. Check your HTML/JS/CSS codes against bad codes."
|
|
34
|
+
s.author = "qhwa,bencode"
|
|
35
|
+
s.email = "qhwa@163.com,bencode@163.com"
|
|
36
|
+
s.homepage = "https://github.com/qhwa/fdlint"
|
|
37
|
+
|
|
38
|
+
s.has_rdoc = false
|
|
39
|
+
s.extra_rdoc_files = %w(README.md)
|
|
40
|
+
s.rdoc_options = %w(--main README.md)
|
|
41
|
+
|
|
42
|
+
# Add any extra files to include in the gem
|
|
43
|
+
s.files = %w(README.md Rakefile Gemfile.lock Gemfile) +
|
|
44
|
+
Dir.glob("{bin,test,lib,rules.d}/**/*") -
|
|
45
|
+
Dir.glob("test/fixtures/html/{cms,tmp}/**/*")
|
|
46
|
+
s.executables = FileList["bin/**"].map { |f| File.basename(f) }
|
|
47
|
+
s.require_paths = ["lib"]
|
|
48
|
+
|
|
49
|
+
# If you want to depend on other gems, add them here, along with any
|
|
50
|
+
# relevant versions
|
|
51
|
+
s.add_dependency("win32console")
|
|
52
|
+
|
|
53
|
+
# If your tests use any gems, include them here
|
|
54
|
+
s.add_development_dependency("rake")
|
|
55
|
+
s.add_development_dependency("test-unit")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# This task actually builds the gem. We also regenerate a static
|
|
59
|
+
# .gemspec file, which is useful if something (i.e. GitHub) will
|
|
60
|
+
# be automatically building a gem for this project. If you're not
|
|
61
|
+
# using GitHub, edit as appropriate.
|
|
62
|
+
#
|
|
63
|
+
# To publish your gem online, install the 'gemcutter' gem; Read more
|
|
64
|
+
# about that here: http://gemcutter.org/pages/gem_docs
|
|
65
|
+
Gem::PackageTask.new(spec) do |pkg|
|
|
66
|
+
pkg.gem_spec = spec
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
desc "Build the gemspec file #{spec.name}.gemspec"
|
|
70
|
+
task :gemspec do
|
|
71
|
+
file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
|
|
72
|
+
File.open(file, "w") {|f| f << spec.to_ruby }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# If you don't want to generate the .gemspec file, just remove this line. Reasons
|
|
76
|
+
# why you might want to generate a gemspec:
|
|
77
|
+
# - using bundler with a git source
|
|
78
|
+
# - building the gem without rake (i.e. gem build blah.gemspec)
|
|
79
|
+
# - maybe others?
|
|
80
|
+
task :package => :gemspec
|
|
81
|
+
|
|
82
|
+
# Generate documentation
|
|
83
|
+
RDoc::Task.new do |rd|
|
|
84
|
+
rd.main = "README.md"
|
|
85
|
+
rd.rdoc_files.include("README.md", "lib/**/*.rb")
|
|
86
|
+
rd.rdoc_dir = "rdoc"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
desc 'Clear out RDoc and generated packages'
|
|
90
|
+
task :clean => [:clobber_rdoc, :clobber_package] do
|
|
91
|
+
rm "#{spec.name}.gemspec"
|
|
92
|
+
end
|
data/bin/fdlint
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
unless Kernel.respond_to?(:require_relative)
|
|
4
|
+
module Kernel
|
|
5
|
+
def require_relative(path)
|
|
6
|
+
require File.expand_path(path.to_str, File.dirname(caller[0]))
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative '../lib/cmd_runner'
|
|
12
|
+
|
|
13
|
+
if __FILE__ == $0
|
|
14
|
+
trap("SIGINT") { puts "\nGoodbye!"; exit! }
|
|
15
|
+
XRay::CMDRunner.run
|
|
16
|
+
end
|
|
17
|
+
|
data/lib/base_parser.rb
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
|
|
3
|
+
require_relative 'node'
|
|
4
|
+
require_relative 'parse_error'
|
|
5
|
+
require_relative 'position_info'
|
|
6
|
+
|
|
7
|
+
module XRay
|
|
8
|
+
|
|
9
|
+
class BaseParser
|
|
10
|
+
|
|
11
|
+
attr_reader :log
|
|
12
|
+
|
|
13
|
+
def initialize(text, log = nil)
|
|
14
|
+
@log = log
|
|
15
|
+
text = filter_text(prepare_text(text))
|
|
16
|
+
@pos_info = PositionInfo.new text
|
|
17
|
+
@scanner = StringScanner.new text
|
|
18
|
+
@text_size = text.size
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def skip_empty
|
|
22
|
+
@scanner.skip(/\s*/)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def skip(pattern, not_skip_empty = false)
|
|
26
|
+
not_skip_empty || skip_empty
|
|
27
|
+
pos = scanner_pos
|
|
28
|
+
unless @scanner.skip pattern
|
|
29
|
+
parse_error pattern
|
|
30
|
+
end
|
|
31
|
+
after_skip pattern
|
|
32
|
+
pos
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check(pattern, not_skip_empty = false)
|
|
36
|
+
skip_empty = !not_skip_empty
|
|
37
|
+
if skip_empty && @scanner.check(/\s+/)
|
|
38
|
+
last_pos = @scanner.pos
|
|
39
|
+
@scanner.skip /\s+/
|
|
40
|
+
end
|
|
41
|
+
ret = @scanner.check pattern
|
|
42
|
+
@scanner.pos = last_pos if last_pos
|
|
43
|
+
ret
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def scan(pattern, not_skip_empty = false)
|
|
47
|
+
node = raw_scan pattern, not_skip_empty
|
|
48
|
+
after_scan pattern
|
|
49
|
+
node
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def raw_scan(pattern, not_skip_empty = false)
|
|
53
|
+
not_skip_empty || skip_empty
|
|
54
|
+
pos = scanner_pos
|
|
55
|
+
text = @scanner.scan pattern
|
|
56
|
+
text ? Node.new(text, pos) : parse_error(pattern)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def batch(name, stop = nil, skip_pattern = nil, not_skip_empty = false, &block)
|
|
60
|
+
first = true
|
|
61
|
+
result = []
|
|
62
|
+
while !@scanner.eos? &&
|
|
63
|
+
(stop ? batch_check(stop, skip_pattern, not_skip_empty, first) :
|
|
64
|
+
block ? block.call : true) &&
|
|
65
|
+
item = send(name)
|
|
66
|
+
result << item
|
|
67
|
+
first = false
|
|
68
|
+
end
|
|
69
|
+
result
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_s
|
|
73
|
+
self.class.to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def reset
|
|
77
|
+
@scanner.reset
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def eos?
|
|
81
|
+
@scanner.eos?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
|
|
86
|
+
def filter_text(text)
|
|
87
|
+
text
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def after_skip(pattern)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def after_scan(pattern)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse_warn(message)
|
|
97
|
+
pos = scanner_pos
|
|
98
|
+
log "#{message}#{pos}", :warn
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_error(pattern)
|
|
102
|
+
if pattern.respond_to?(:source)
|
|
103
|
+
message = "should be #{pattern.source} here"
|
|
104
|
+
else
|
|
105
|
+
message = pattern.to_s
|
|
106
|
+
end
|
|
107
|
+
pos = scanner_pos
|
|
108
|
+
log "#{message} #{pos}", :error
|
|
109
|
+
raise ParseError.new(message, pos)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def log(message, level = :info)
|
|
113
|
+
@log && @log.send("#{level}?") &&
|
|
114
|
+
@log.send(level, self.to_s + ': ' + message)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def scanner_pos
|
|
118
|
+
pos = @text_size - @scanner.rest.size
|
|
119
|
+
@pos_info.locate pos
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def prepare_text(text)
|
|
125
|
+
if text.respond_to? :encode!
|
|
126
|
+
text.encode! 'utf-8', :invalid => :replace, :universal_newline => true
|
|
127
|
+
else
|
|
128
|
+
text.gsub(/\r\n/, "\n").gsub(/\r/, "\n")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def batch_check(stop, skip_pattern, not_skip_empty, first)
|
|
133
|
+
if check stop, not_skip_empty
|
|
134
|
+
false
|
|
135
|
+
else
|
|
136
|
+
!first && skip_pattern && skip(skip_pattern)
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
data/lib/cmd_runner.rb
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'optparse'
|
|
3
|
+
require 'find'
|
|
4
|
+
require_relative 'runner'
|
|
5
|
+
require_relative 'printer/vim_printer'
|
|
6
|
+
require_relative 'printer/console_printer'
|
|
7
|
+
require_relative 'printer/nocolor_printer'
|
|
8
|
+
|
|
9
|
+
module XRay
|
|
10
|
+
|
|
11
|
+
Version = "0.1"
|
|
12
|
+
|
|
13
|
+
class CMDOptions
|
|
14
|
+
|
|
15
|
+
def self.parse( args )
|
|
16
|
+
files = []
|
|
17
|
+
options = {
|
|
18
|
+
:encoding => 'gb2312',
|
|
19
|
+
:colorful => true,
|
|
20
|
+
:type => nil,
|
|
21
|
+
:check_min => false,
|
|
22
|
+
:format => :console
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
opts = OptionParser.new do |opts|
|
|
26
|
+
opts.banner = "Usage: fdlint"
|
|
27
|
+
%w(css js html).each do |type|
|
|
28
|
+
opts.on("--#{type}", "check #{type} files only") do
|
|
29
|
+
options[:type] = type.intern
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
opts.on("--charset set", "-c", "file charset") do |enc|
|
|
33
|
+
options[:encoding] = enc
|
|
34
|
+
end
|
|
35
|
+
opts.on("--debug", "-d", "print debug info") do
|
|
36
|
+
options[:debug] = true
|
|
37
|
+
end
|
|
38
|
+
opts.on("--list", "-l", "list results without source, the same as '--format=nocolor'") do
|
|
39
|
+
options[:format] = :nocolor
|
|
40
|
+
end
|
|
41
|
+
opts.on("--checkmin", "-m", "check minified files too. (e.g. *-min.js; *-min.css)") do
|
|
42
|
+
options[:check_min] = true
|
|
43
|
+
end
|
|
44
|
+
opts.on("--format [type]", [:console, :nocolor, :vim], "output format. Can be 'vim', 'console' or 'nocolor'. Default is 'console'") do |f|
|
|
45
|
+
options[:format] = f.intern
|
|
46
|
+
end
|
|
47
|
+
opts.on("--level [log_level]", [:warn, :error, :fatal], "determine the log level. Can be 'warn', 'error' or 'fatal'") do |level|
|
|
48
|
+
options[:log_level] = level
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
rest = opts.parse! args
|
|
54
|
+
files.concat rest
|
|
55
|
+
|
|
56
|
+
if files.empty?
|
|
57
|
+
unless $stdin.tty?
|
|
58
|
+
str = ARGF.read
|
|
59
|
+
options[:text] = str
|
|
60
|
+
else
|
|
61
|
+
raise ArgumentError.new("")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
rescue => e
|
|
66
|
+
puts e.message.capitalize + "\n\n"
|
|
67
|
+
puts opts
|
|
68
|
+
exit 1
|
|
69
|
+
end
|
|
70
|
+
[options, files]
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class CMDRunner
|
|
77
|
+
|
|
78
|
+
def self.run
|
|
79
|
+
self.new.run
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def run
|
|
83
|
+
options, files = XRay::CMDOptions.parse ARGV
|
|
84
|
+
@core_runner = XRay::Runner.new(options)
|
|
85
|
+
|
|
86
|
+
unless files.empty?
|
|
87
|
+
files.each do |file|
|
|
88
|
+
check_file file, options
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
method = options[:type] ? :"check_#{options[:type]}" : :check
|
|
92
|
+
print @core_runner.send(method, options[:text].utf8!), options
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
def check_file( file, opt)
|
|
98
|
+
|
|
99
|
+
if File.directory? file
|
|
100
|
+
Find.find(file) do |f|
|
|
101
|
+
check_file(f, opt) unless File.directory? f
|
|
102
|
+
end
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if @core_runner.valid_file? file
|
|
107
|
+
print @core_runner.check_file( file ), opt.merge( :file => file.to_s )
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def print( results, opt={} )
|
|
112
|
+
print_results(
|
|
113
|
+
results,
|
|
114
|
+
opt.merge({
|
|
115
|
+
:prefix => ' ' * 5,
|
|
116
|
+
:source => @core_runner.source
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def print_results( results, opt={} )
|
|
122
|
+
IO.popen "chcp 65001" if ENV['OS'] =~ /windows/i
|
|
123
|
+
print_class = case opt[:format]
|
|
124
|
+
when :vim
|
|
125
|
+
'VimPrinter'
|
|
126
|
+
when :nocolor
|
|
127
|
+
'NoColorPrinter'
|
|
128
|
+
else
|
|
129
|
+
'ConsolePrinter'
|
|
130
|
+
end
|
|
131
|
+
XRay.register_printer XRay.const_get( print_class )
|
|
132
|
+
XRay.printer.new( results, opt ).print
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def print_type(format)
|
|
136
|
+
format || 'base'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if $0 == __FILE__
|
|
144
|
+
XRay::CMDRunner.run
|
|
145
|
+
end
|
data/lib/context.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module XRay
|
|
2
|
+
|
|
3
|
+
module Context
|
|
4
|
+
|
|
5
|
+
attr_accessor :scope
|
|
6
|
+
|
|
7
|
+
def lib?
|
|
8
|
+
scope == :lib
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def page_level?
|
|
12
|
+
scope == :page
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def in_page?
|
|
16
|
+
scope == :in_page
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class DefaultContext
|
|
22
|
+
|
|
23
|
+
include Context
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@scope = :page
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
end
|
data/lib/css/parser.rb
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require_relative '../base_parser'
|
|
2
|
+
require_relative 'struct'
|
|
3
|
+
|
|
4
|
+
module XRay
|
|
5
|
+
module CSS
|
|
6
|
+
|
|
7
|
+
class Parser < XRay::BaseParser
|
|
8
|
+
TERM = %q([^;{}'"])
|
|
9
|
+
TERM2 = %q([^;{}'",])
|
|
10
|
+
QUOT_EXPR = "'[^']*'"
|
|
11
|
+
DQUOT_EXPR = '"[^"]*"'
|
|
12
|
+
|
|
13
|
+
R_IDENT = /-?[_a-z][_a-z0-9-]*/
|
|
14
|
+
R_ANY = %r"((?:#{TERM})|(?:#{QUOT_EXPR})|(?:#{DQUOT_EXPR}))+"
|
|
15
|
+
R_SELECTOR = %r"((?:#{TERM2})|(?:#{QUOT_EXPR})|(?:#{DQUOT_EXPR}))+"
|
|
16
|
+
R_PROPERTY = /[*_+\\]?-?[_a-z\\][\\_a-z0-9-]*/
|
|
17
|
+
|
|
18
|
+
attr_reader :comments
|
|
19
|
+
|
|
20
|
+
def initialize(css, logger)
|
|
21
|
+
super
|
|
22
|
+
@comments = []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse_stylesheet(inner = false)
|
|
26
|
+
log 'parse stylesheet'
|
|
27
|
+
|
|
28
|
+
do_parse_comment
|
|
29
|
+
|
|
30
|
+
stats = batch(:parse_statement) do
|
|
31
|
+
skip_empty
|
|
32
|
+
!(inner ? check(/\}/) : eos?)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
StyleSheet.new stats
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias_method :parse, :parse_stylesheet
|
|
39
|
+
|
|
40
|
+
# ruleset or directive
|
|
41
|
+
def parse_statement
|
|
42
|
+
log 'parse statement'
|
|
43
|
+
|
|
44
|
+
if check /@/
|
|
45
|
+
parse_directive
|
|
46
|
+
else
|
|
47
|
+
parse_ruleset
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_directive
|
|
52
|
+
log 'parse directive'
|
|
53
|
+
|
|
54
|
+
skip /@/
|
|
55
|
+
keyword = scan R_IDENT
|
|
56
|
+
skip_empty
|
|
57
|
+
|
|
58
|
+
expr = check(/\{/) || check(/;/) ? nil :
|
|
59
|
+
scan(R_ANY)
|
|
60
|
+
|
|
61
|
+
block = nil
|
|
62
|
+
if check /\{/
|
|
63
|
+
skip /\{/
|
|
64
|
+
block = parse_stylesheet true
|
|
65
|
+
skip /\}/
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless block
|
|
69
|
+
skip /;/
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
log " keyword: #{keyword} #{keyword.position}"
|
|
73
|
+
log(" expression: #{expr} #{expr.position}") if expr
|
|
74
|
+
log(" block:\n#{block}\n#{block.position}") if block
|
|
75
|
+
Directive.new keyword, expr, block
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_ruleset
|
|
79
|
+
log 'parse ruleset'
|
|
80
|
+
|
|
81
|
+
selector = check(/\{/) ? nil : parse_selector
|
|
82
|
+
skip /\{/
|
|
83
|
+
declarations = do_parse_declarations
|
|
84
|
+
skip /\}/
|
|
85
|
+
|
|
86
|
+
RuleSet.new selector, declarations
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_selector
|
|
90
|
+
log ' parse selector'
|
|
91
|
+
simple_selectors = batch(:parse_simple_selector, /\{/, /,/)
|
|
92
|
+
Selector.new simple_selectors
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_simple_selector
|
|
96
|
+
log ' parse simple selector'
|
|
97
|
+
selector = scan R_SELECTOR
|
|
98
|
+
log " #{selector} #{selector.position}"
|
|
99
|
+
selector
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_declarations
|
|
103
|
+
first = true
|
|
104
|
+
batch(:parse_declaration) do
|
|
105
|
+
if check /\}/
|
|
106
|
+
false
|
|
107
|
+
else
|
|
108
|
+
skip(first ? /[;\s]*/ : /[;\s]+/)
|
|
109
|
+
first = false
|
|
110
|
+
!check /\}/
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def parse_declaration
|
|
116
|
+
log ' parse declaration'
|
|
117
|
+
|
|
118
|
+
property = parse_property
|
|
119
|
+
skip /:/
|
|
120
|
+
value = parse_value
|
|
121
|
+
|
|
122
|
+
Declaration.new(property, value)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def parse_property
|
|
126
|
+
log ' parse property'
|
|
127
|
+
property = scan R_PROPERTY
|
|
128
|
+
log " #{property} #{property.position}"
|
|
129
|
+
property
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_value
|
|
133
|
+
log ' parse value'
|
|
134
|
+
|
|
135
|
+
value = scan R_ANY
|
|
136
|
+
log " #{value} #{value.position}"
|
|
137
|
+
value
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_comment
|
|
141
|
+
log 'pare comment'
|
|
142
|
+
comment = raw_scan /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//
|
|
143
|
+
log " #{comment}"
|
|
144
|
+
comment
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
protected
|
|
148
|
+
|
|
149
|
+
#override
|
|
150
|
+
def after_scan(pattern)
|
|
151
|
+
do_parse_comment
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def after_skip(pattern)
|
|
155
|
+
do_parse_comment
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
def do_parse_declarations
|
|
161
|
+
first = true
|
|
162
|
+
batch(:parse_declaration) do
|
|
163
|
+
if check /\}/
|
|
164
|
+
false
|
|
165
|
+
else
|
|
166
|
+
skip(first ? /[;\s]*/ : /[;\s]+/)
|
|
167
|
+
first = false
|
|
168
|
+
!check /\}/
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def do_parse_comment
|
|
174
|
+
while true
|
|
175
|
+
if check /\/\*/
|
|
176
|
+
@comments << parse_comment
|
|
177
|
+
else
|
|
178
|
+
break
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
end
|
|
186
|
+
end
|