fdlint 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +14 -0
  3. data/README.md +68 -0
  4. data/Rakefile +92 -0
  5. data/bin/fdlint +17 -0
  6. data/lib/base_parser.rb +143 -0
  7. data/lib/cmd_runner.rb +145 -0
  8. data/lib/context.rb +31 -0
  9. data/lib/css/parser.rb +186 -0
  10. data/lib/css/reader.rb +30 -0
  11. data/lib/css/rule/check_compression_rule.rb +48 -0
  12. data/lib/css/rule/checklist.rb +45 -0
  13. data/lib/css/struct.rb +111 -0
  14. data/lib/encoding_error.rb +6 -0
  15. data/lib/file_validator.rb +38 -0
  16. data/lib/helper/code_type.rb +50 -0
  17. data/lib/helper/color_string.rb +44 -0
  18. data/lib/helper/file_reader.rb +22 -0
  19. data/lib/helper/strenc.rb +65 -0
  20. data/lib/html/parser.rb +212 -0
  21. data/lib/html/query.rb +96 -0
  22. data/lib/html/rule/check_tag_rule.rb +80 -0
  23. data/lib/html/struct.rb +291 -0
  24. data/lib/js/expr/expr.rb +66 -0
  25. data/lib/js/expr/left_hand.rb +63 -0
  26. data/lib/js/expr/operate.rb +92 -0
  27. data/lib/js/expr/primary.rb +166 -0
  28. data/lib/js/parser.rb +116 -0
  29. data/lib/js/rule/all.rb +35 -0
  30. data/lib/js/rule/checklist.rb +41 -0
  31. data/lib/js/rule/file_checker.rb +42 -0
  32. data/lib/js/rule/helper.rb +96 -0
  33. data/lib/js/rule/no_global.rb +87 -0
  34. data/lib/js/stat/if.rb +25 -0
  35. data/lib/js/stat/iter.rb +85 -0
  36. data/lib/js/stat/stat.rb +117 -0
  37. data/lib/js/stat/switch.rb +65 -0
  38. data/lib/js/stat/try.rb +28 -0
  39. data/lib/js/stat/var.rb +40 -0
  40. data/lib/js/struct.rb +248 -0
  41. data/lib/log_entry.rb +49 -0
  42. data/lib/node.rb +28 -0
  43. data/lib/parse_error.rb +13 -0
  44. data/lib/parser_visitable.rb +138 -0
  45. data/lib/position_info.rb +46 -0
  46. data/lib/printer/base_printer.rb +24 -0
  47. data/lib/printer/console_printer.rb +66 -0
  48. data/lib/printer/nocolor_printer.rb +27 -0
  49. data/lib/printer/vim_printer.rb +19 -0
  50. data/lib/rule.rb +241 -0
  51. data/lib/rule_helper.rb +14 -0
  52. data/lib/runner.rb +225 -0
  53. data/rules.d/css.rule +127 -0
  54. data/rules.d/html.dtd.rule +22 -0
  55. data/rules.d/html.prop.rule +51 -0
  56. data/rules.d/html.tag.rule +136 -0
  57. data/rules.d/js.file.rule +13 -0
  58. data/rules.d/js.jquery.rule +56 -0
  59. data/rules.d/js.mergefile.rule +71 -0
  60. data/rules.d/js.rule +84 -0
  61. data/test/all_tests.rb +84 -0
  62. data/test/cli/cli_test.rb +70 -0
  63. data/test/cli/log_level_test.rb +51 -0
  64. data/test/cli/output_format_test.rb +47 -0
  65. data/test/cli/type_test.rb +77 -0
  66. data/test/css/mac_line_end_support_test.rb +38 -0
  67. data/test/css/parser_test.rb +276 -0
  68. data/test/css/rule/check_encoding_test.rb +66 -0
  69. data/test/css/rule/check_list_rule_test.rb +167 -0
  70. data/test/css/rule/compression_test.rb +53 -0
  71. data/test/css/rule/file_name_test.rb +76 -0
  72. data/test/fixtures/css/broken.css +4 -0
  73. data/test/fixtures/css/cbu/36.css +52 -0
  74. data/test/fixtures/css/cbu/china_top.css +324 -0
  75. data/test/fixtures/css/cbu/default-merge.css +3 -0
  76. data/test/fixtures/css/cbu/default.css +13 -0
  77. data/test/fixtures/css/cbu/diy-merge.css +25 -0
  78. data/test/fixtures/css/cbu/fns-v1.css +27 -0
  79. data/test/fixtures/css/cbu/index_v0.1.css +12 -0
  80. data/test/fixtures/css/cbu/merge.css +11 -0
  81. data/test/fixtures/css/cbu/min.css +2 -0
  82. data/test/fixtures/css/cbu/my_home_admin.css +126 -0
  83. data/test/fixtures/css/cbu/nav.css +95 -0
  84. data/test/fixtures/css/cbu/pic_list.css +386 -0
  85. data/test/fixtures/css/cbu/quote-edit.css +18 -0
  86. data/test/fixtures/css/cbu/selloffer.shopwindow.css +1 -0
  87. data/test/fixtures/css/cbu/v1.css +9 -0
  88. data/test/fixtures/css/css3.css +30 -0
  89. data/test/fixtures/css/empty-min.css +0 -0
  90. data/test/fixtures/css/empty.css +0 -0
  91. data/test/fixtures/css/font-family.css +4 -0
  92. data/test/fixtures/css/gb-good.css +14 -0
  93. data/test/fixtures/css/gb_using_star.css +4 -0
  94. data/test/fixtures/css/import.css +18 -0
  95. data/test/fixtures/css/mac-line-sep-err-min.css +1 -0
  96. data/test/fixtures/css/mac-line-sep-err.css +1 -0
  97. data/test/fixtures/css/mac-line-sep-good-min.css +1 -0
  98. data/test/fixtures/css/mac-line-sep-good.css +1 -0
  99. data/test/fixtures/css/multi-encoding-in-a-file.css +0 -0
  100. data/test/fixtures/css/simple.css +1 -0
  101. data/test/fixtures/css/using_expr.css +8 -0
  102. data/test/fixtures/css/using_hack.css +21 -0
  103. data/test/fixtures/css/using_id.css +1 -0
  104. data/test/fixtures/css/using_star.css +4 -0
  105. data/test/fixtures/css/utf8_good.css +6 -0
  106. data/test/fixtures/css/utf8_good_declaring_charset.css +7 -0
  107. data/test/fixtures/css/utf8_using_star.css +5 -0
  108. data/test/fixtures/html/1-1.html +120 -0
  109. data/test/fixtures/html/1-2.html +120 -0
  110. data/test/fixtures/html/cms.html +373 -0
  111. data/test/fixtures/html/css_out_of_head.html +9 -0
  112. data/test/fixtures/html/fdev-template.html +22 -0
  113. data/test/fixtures/html/google.com.html +33 -0
  114. data/test/fixtures/html/mixed_log_levels.html +4 -0
  115. data/test/fixtures/html/mixed_types.html +13 -0
  116. data/test/fixtures/html/no_dtd.html +6 -0
  117. data/test/fixtures/html/readme.html +94 -0
  118. data/test/fixtures/html/review.board.html +163 -0
  119. data/test/fixtures/html/syntax_err.html +3 -0
  120. data/test/fixtures/html/train/detail/345/233/276/346/226/207/347/273/223/345/220/210.html +208 -0
  121. data/test/fixtures/html/train/detail/347/232/204Flash.html +212 -0
  122. data/test/fixtures/html/train/detail/347/232/204Vedio.html +212 -0
  123. data/test/fixtures/html/train/index.html +37 -0
  124. data/test/fixtures/html/train/test.html +1 -0
  125. 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
  126. data/test/fixtures/html/train//344/277/256/346/224/271/345/255/220/345/210/206/347/261/273.html +108 -0
  127. data/test/fixtures/html/train//344/277/256/346/224/271/350/257/276/347/250/213.html +195 -0
  128. data/test/fixtures/html/train//345/215/232/345/256/242/350/256/276/347/275/256.html +142 -0
  129. data/test/fixtures/html/train//346/265/217/350/247/210/350/256/260/345/275/225.html +191 -0
  130. 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
  131. data/test/fixtures/html/train//346/267/273/345/212/240/345/255/220/345/210/206/347/261/273.html +112 -0
  132. data/test/fixtures/html/train//346/267/273/345/212/240/350/257/276/347/250/213.html +195 -0
  133. data/test/fixtures/html/train//347/231/273/345/275/225.html +20 -0
  134. data/test/fixtures/html/train//347/256/241/347/220/206/345/210/206/347/261/273.html +210 -0
  135. data/test/fixtures/html/train//347/256/241/347/220/206/345/217/215/351/246/210.html +222 -0
  136. data/test/fixtures/html/train//347/256/241/347/220/206/350/257/276/347/250/213.html +284 -0
  137. data/test/fixtures/html/train//347/256/241/347/220/206/350/264/246/346/210/267.html +107 -0
  138. data/test/fixtures/html/train//347/275/221/344/270/212/345/237/271/350/256/255home/351/241/265.html +354 -0
  139. data/test/fixtures/html/train//347/275/221/345/225/206/345/237/271/350/256/255list/351/241/265.html +255 -0
  140. 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
  141. data/test/fixtures/html/train//350/257/264/346/230/216.txt +3 -0
  142. 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
  143. data/test/fixtures/html/unescaped.html +2 -0
  144. data/test/fixtures/html/view.vm +916 -0
  145. data/test/fixtures/js/jquery-1.7.js +9300 -0
  146. data/test/fixtures/js/scope-test.js +22 -0
  147. data/test/helper.rb +41 -0
  148. data/test/html/mixed_type_test.rb +35 -0
  149. data/test/html/parser/parse_comment_test.rb +47 -0
  150. data/test/html/parser/parse_dtd_test.rb +46 -0
  151. data/test/html/parser/parse_script_tag_test.rb +55 -0
  152. data/test/html/parser/parse_with_auto_close_tag_test.rb +41 -0
  153. data/test/html/parser/parse_with_diff_case_test.rb +38 -0
  154. data/test/html/parser/parse_with_emtpy_test.rb +22 -0
  155. data/test/html/parser/parse_with_multi_children_test.rb +27 -0
  156. data/test/html/parser/parse_with_multi_line_test.rb +41 -0
  157. data/test/html/parser/parse_with_prop_test.rb +88 -0
  158. data/test/html/parser/parse_with_script_tag_test.rb +26 -0
  159. data/test/html/parser/parse_with_selfclosing_test.rb +39 -0
  160. data/test/html/parser/parse_with_simple_tag_test.rb +44 -0
  161. data/test/html/parser/parse_with_simple_tree_test.rb +40 -0
  162. data/test/html/parser/parse_with_style_tag_test.rb +22 -0
  163. data/test/html/parser/parse_with_text_test.rb +45 -0
  164. data/test/html/parser_test.rb +52 -0
  165. data/test/html/query_test.rb +52 -0
  166. data/test/html/rule/check_block_level_element_test.rb +52 -0
  167. data/test/html/rule/check_button_test.rb +45 -0
  168. data/test/html/rule/check_class_count_test.rb +36 -0
  169. data/test/html/rule/check_css_in_head_test.rb +53 -0
  170. data/test/html/rule/check_dtd_test.rb +46 -0
  171. data/test/html/rule/check_form_element_name_test.rb +49 -0
  172. data/test/html/rule/check_head_contain_meta_and_title_test.rb +52 -0
  173. data/test/html/rule/check_html_template_test.rb +103 -0
  174. data/test/html/rule/check_hyperlink_with_target_test.rb +40 -0
  175. data/test/html/rule/check_hyperlink_with_title_test.rb +43 -0
  176. data/test/html/rule/check_id_n_class_downcase_test.rb +40 -0
  177. data/test/html/rule/check_img_with_alt_prop_test.rb +33 -0
  178. data/test/html/rule/check_no_import_css_test.rb +36 -0
  179. data/test/html/rule/check_prop_have_value_test.rb +32 -0
  180. data/test/html/rule/check_prop_seperator_test.rb +32 -0
  181. data/test/html/rule/check_style_prop_test.rb +30 -0
  182. data/test/html/rule/check_tag_closed_test.rb +59 -0
  183. data/test/html/rule/check_tag_downcase_test.rb +51 -0
  184. data/test/html/rule/check_unescape_char_test.rb +35 -0
  185. data/test/html/rule/check_unique_import_test.rb +56 -0
  186. data/test/html/rule_test.rb +62 -0
  187. data/test/js/expr/expr.rb +57 -0
  188. data/test/js/expr/left_hand.rb +25 -0
  189. data/test/js/expr/operate.rb +145 -0
  190. data/test/js/expr/primary.rb +89 -0
  191. data/test/js/parser_test.rb +98 -0
  192. data/test/js/rule/alert_check_test.rb +37 -0
  193. data/test/js/rule/all_test.rb +23 -0
  194. data/test/js/rule/base_test.rb +34 -0
  195. data/test/js/rule/file_checker_test.rb +131 -0
  196. data/test/js/rule/jq_check_test.rb +90 -0
  197. data/test/js/rule/nest_try_catch_test.rb +71 -0
  198. data/test/js/rule/new_object_and_new_array_test.rb +38 -0
  199. data/test/js/rule/no_eval_test.rb +34 -0
  200. data/test/js/rule/no_global_test.rb +88 -0
  201. data/test/js/rule/private_method_check_test.rb +58 -0
  202. data/test/js/rule/semicolon_test.rb +63 -0
  203. data/test/js/rule/stat_if_with_brace_test.rb +68 -0
  204. data/test/js/rule/stat_if_with_muti_else_test.rb +68 -0
  205. data/test/js/rule/use_strict_equal_test.rb +44 -0
  206. data/test/js/rule_test.rb +47 -0
  207. data/test/js/stat/if.rb +26 -0
  208. data/test/js/stat/iter.rb +115 -0
  209. data/test/js/stat/stat.rb +91 -0
  210. data/test/js/stat/switch.rb +37 -0
  211. data/test/js/stat/try.rb +32 -0
  212. data/test/js/stat/var.rb +38 -0
  213. data/test/parser_visitable_test.rb +102 -0
  214. data/test/position_info_test.rb +66 -0
  215. data/test/rule_dsl/dsl_basic_test.rb +91 -0
  216. data/test/rule_dsl/importing_test.rb +48 -0
  217. data/test/runner/log_level_test.rb +58 -0
  218. metadata +317 -0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+ gem 'win32console' if ENV['OS'] =~ /windows/i
3
+
4
+ group :development do
5
+ gem 'test-unit'
6
+ gem 'rake'
7
+ gem 'gem-this'
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ gem-this (0.3.7)
5
+ rake (0.9.2.2)
6
+ test-unit (2.4.8)
7
+
8
+ PLATFORMS
9
+ ruby
10
+
11
+ DEPENDENCIES
12
+ gem-this
13
+ rake
14
+ test-unit
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
+
@@ -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