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.
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