fdlint 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![fdlint-logo](http://q.pnq.cc/wp-content/uploads/2012/02/fdlint-logo-white.png)
|
5
|
+
|
6
|
+
fdlint (开发代号xray) 是根据阿里巴巴前端开发checklist开发的自动代码扫描工具。
|
7
|
+
可以扫描出前端程序中不符合开发规范的地方。
|
8
|
+
|
9
|
+
[![Build Status](https://secure.travis-ci.org/qhwa/fdlint.png)](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
|