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/lib/runner.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#require 'profile'
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require_relative 'base_parser'
|
7
|
+
require_relative 'rule'
|
8
|
+
require_relative 'parser_visitable'
|
9
|
+
require_relative 'file_validator'
|
10
|
+
require_relative 'log_entry'
|
11
|
+
require_relative 'css/reader'
|
12
|
+
require_relative 'css/parser'
|
13
|
+
require_relative 'css/rule/checklist'
|
14
|
+
require_relative 'html/parser'
|
15
|
+
require_relative 'html/rule/check_tag_rule'
|
16
|
+
require_relative 'js/parser'
|
17
|
+
require_relative 'js/rule/all'
|
18
|
+
require_relative 'js/rule/file_checker'
|
19
|
+
require_relative 'helper/file_reader'
|
20
|
+
require_relative 'helper/code_type'
|
21
|
+
|
22
|
+
|
23
|
+
module XRay
|
24
|
+
|
25
|
+
module CSS
|
26
|
+
class VisitableParser < Parser
|
27
|
+
include XRay::ParserVisitable
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module HTML
|
32
|
+
class VisitableParser < Parser
|
33
|
+
include XRay::ParserVisitable
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module JS
|
38
|
+
class VisitableParser < Parser
|
39
|
+
include XRay::ParserVisitable
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
class Runner
|
45
|
+
|
46
|
+
include Helper::FileReader
|
47
|
+
|
48
|
+
attr_reader :source, :rules
|
49
|
+
|
50
|
+
def initialize(opt={})
|
51
|
+
@opt = {
|
52
|
+
:encoding => 'utf-8',
|
53
|
+
:debug => false,
|
54
|
+
:log_level => :warn
|
55
|
+
}.merge opt
|
56
|
+
|
57
|
+
@rules = []
|
58
|
+
|
59
|
+
if @opt[:debug]
|
60
|
+
@logger = Logger.new(STDOUT)
|
61
|
+
@logger.level = Logger::INFO
|
62
|
+
end
|
63
|
+
|
64
|
+
Rule.import_all
|
65
|
+
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_css(css, opt = {})
|
70
|
+
@source = css
|
71
|
+
parser = XRay::CSS::VisitableParser.new(css, @logger)
|
72
|
+
visitor = XRay::CSS::Rule::CheckListRule.new( opt )
|
73
|
+
parser.add_visitor visitor
|
74
|
+
run_parser( parser )
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_css_file( file, opt={} )
|
78
|
+
results = []
|
79
|
+
begin
|
80
|
+
"".extend(XRay::Rule).check_css_file(file).each do |msg, level, *pos|
|
81
|
+
results.unshift LogEntry.new( msg, level, pos[0] || 0, pos[1] || 0 )
|
82
|
+
end
|
83
|
+
|
84
|
+
source = XRay::CSS::Reader.read( file, @opt )
|
85
|
+
results.concat check_css( source, opt.merge({
|
86
|
+
:scope => CodeType.scope(file)
|
87
|
+
}))
|
88
|
+
rescue EncodingError => e
|
89
|
+
results.unshift LogEntry.new( "File can't be read as #{@opt[:encoding]} charset", :fatal)
|
90
|
+
rescue => e
|
91
|
+
if @opt[:debug]
|
92
|
+
puts e
|
93
|
+
puts e.backtrace
|
94
|
+
end
|
95
|
+
results.unshift LogEntry.new( e.to_s, :fatal)
|
96
|
+
end
|
97
|
+
results
|
98
|
+
end
|
99
|
+
|
100
|
+
def check_js(js, opt={})
|
101
|
+
@source = js
|
102
|
+
parser = JS::VisitableParser.new(js, @logger)
|
103
|
+
parser.add_visitors JS::Rule::All.new
|
104
|
+
run_parser( parser )
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_js_file(file, opt = {})
|
108
|
+
results = []
|
109
|
+
begin
|
110
|
+
results.concat JS::Rule::FileChecker.new.check_file(file)
|
111
|
+
|
112
|
+
source, encoding = readfile(file, opt)
|
113
|
+
results.concat check_js(source)
|
114
|
+
rescue EncodingError => e
|
115
|
+
results.unshift LogEntry.new( "File can't be read as #{@opt[:encoding]} charset", :fatal)
|
116
|
+
rescue => e
|
117
|
+
if @opt[:debug]
|
118
|
+
puts e
|
119
|
+
puts e.backtrace
|
120
|
+
end
|
121
|
+
results.unshift LogEntry.new( e.to_s, :fatal )
|
122
|
+
end
|
123
|
+
results
|
124
|
+
end
|
125
|
+
|
126
|
+
def check_html(text, opt={})
|
127
|
+
@source = text
|
128
|
+
parser = HTML::VisitableParser.new(text, @logger)
|
129
|
+
visitor = HTML::Rule::CheckTagRule.new( opt )
|
130
|
+
parser.add_visitor visitor
|
131
|
+
results = run_parser( parser )
|
132
|
+
unless @parsed_element.nil? or @parsed_element.empty?
|
133
|
+
results += check_scripts_in_html(opt)
|
134
|
+
results += check_styles_in_html(opt)
|
135
|
+
end
|
136
|
+
results
|
137
|
+
end
|
138
|
+
|
139
|
+
def check_scripts_in_html(opt={})
|
140
|
+
results = []
|
141
|
+
@parsed_element.query('script') do |script|
|
142
|
+
row = script.position.row
|
143
|
+
col = script.position.column
|
144
|
+
Runner.new.check_js(script.text, opt).each do |ret|
|
145
|
+
ret.column += script.outer_html[/^\s*<script*?>/].size if ret.row == 1
|
146
|
+
ret.row += row - 1
|
147
|
+
results << ret
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
results
|
152
|
+
end
|
153
|
+
|
154
|
+
def check_styles_in_html(opt={})
|
155
|
+
results = []
|
156
|
+
@parsed_element.query('style') do |style|
|
157
|
+
row = style.position.row
|
158
|
+
col = style.position.column
|
159
|
+
Runner.new.check_css(style.text, opt.merge({:scope => :in_page })).each do |ret|
|
160
|
+
ret.column += style.outer_html[/^\s*<style*?>/].size if ret.row == 1
|
161
|
+
ret.row += row - 1
|
162
|
+
results << ret
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
results
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_html_file(file, opt={})
|
170
|
+
source, encoding = readfile file
|
171
|
+
check_html source
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_file( file )
|
175
|
+
type = CodeType.guess_by_name(file)
|
176
|
+
send( "check_#{type}_file", file ) if CodeType.is_style_file? file
|
177
|
+
end
|
178
|
+
|
179
|
+
def check(text, filename="")
|
180
|
+
type = CodeType.guess(text, filename)
|
181
|
+
send "check_#{type}", text
|
182
|
+
end
|
183
|
+
|
184
|
+
def valid_file? file
|
185
|
+
CodeType.is_style_file?(file) and !minified_and_ignored(file) and type_ok?(file)
|
186
|
+
end
|
187
|
+
|
188
|
+
def minified_and_ignored(file)
|
189
|
+
!@opt[:check_min] && file.to_s =~ /-min\.(css|js)$/
|
190
|
+
end
|
191
|
+
|
192
|
+
def type_ok?(file)
|
193
|
+
if @opt[:type]
|
194
|
+
CodeType.guess_by_name(file) == @opt[:type]
|
195
|
+
else
|
196
|
+
true
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def run_parser(parser)
|
201
|
+
@parsed_element = parser.parse_no_throw
|
202
|
+
filter_results( parser.results ).sort_by!(&:row)
|
203
|
+
end
|
204
|
+
|
205
|
+
def filter_results(results)
|
206
|
+
case log_level
|
207
|
+
when :warn
|
208
|
+
results
|
209
|
+
when :error
|
210
|
+
results.select {|r| r.level == :error || r.level == :fatal }
|
211
|
+
when :fatal
|
212
|
+
results.select {|r| r.level == :fatal }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def log_level
|
217
|
+
@opt[:log_level]
|
218
|
+
end
|
219
|
+
|
220
|
+
def log_level=(lvl)
|
221
|
+
@opt[:log_level] = lvl
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
data/rules.d/css.rule
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vi: filetype=ruby
|
3
|
+
css
|
4
|
+
|
5
|
+
check_file_name_without_ad do |name|
|
6
|
+
unless defined? ADVERTISMENT
|
7
|
+
ADVERTISMENT = /(?:[^a-z0-9%_-]|^)ad(?:[sv][^a-z\r=\?]+|banner|click|ver|name|x|log|[^a-z\r_-]*[\.\/]|bot|c_|client|council|gifs|graph|images|img|fshow|pic|vert|view|info|click|sponsor)/
|
8
|
+
end
|
9
|
+
if name =~ ADVERTISMENT
|
10
|
+
['路径和文件名中不应该出现ad', :error]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
check_file_name_without_underscore do |name|
|
15
|
+
if File.basename(name) =~ /_/
|
16
|
+
['文件名中单词的分隔符应该使用中横线“-”', :error]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
check_file_name_seperator do |name|
|
21
|
+
if File.dirname(name) =~ /-(?!v?[\d.])/
|
22
|
+
['文件夹只有需要版本区分时才可用中横线分隔,如fdev-v3', :error]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
check_file_name_downcase do |name|
|
27
|
+
win_disk = /^[a-zA-Z]:/
|
28
|
+
if name.sub( win_disk, '') =~ /[A-Z]/
|
29
|
+
['文件夹和文件命名必须用小写字母', :error]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
check_selector_with_id do |selector|
|
34
|
+
if context.page_level? &&
|
35
|
+
selector =~ /#[-\w]+/
|
36
|
+
['页面级别样式不使用id', :error]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
check_selector_with_global_tag do |selector|
|
41
|
+
if context.page_level? &&
|
42
|
+
selector =~ /^\w+$/
|
43
|
+
['页面级别样式不能全局定义标签样式', :error]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
check_selector_level do |selector|
|
48
|
+
if selector.text.split(/[>\s]/).length > 4
|
49
|
+
['CSS级联深度不能超过4层', :error]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
check_selector_with_star do |selector|
|
54
|
+
if selector =~ /^\*/
|
55
|
+
['禁止使用星号选择符', :error]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
check_selector_redefine_lib_css do |selector|
|
60
|
+
if !context.lib? &&
|
61
|
+
selector =~ /^\.fd-\w+$/
|
62
|
+
['禁止修改或重载type中的样式', :error]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
check_declaration_font do |dec|
|
67
|
+
if dec.property =~ /^font(-family)?$/ &&
|
68
|
+
dec.value.to_s.each_byte.any? {|b| b>127}
|
69
|
+
['字体名称中的中文必须用ascii字符表示', :error]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
check_ruleset_redefine_a_hover_color do |ruleset|
|
74
|
+
if ruleset.selector &&
|
75
|
+
ruleset.selector.text == 'a:hover' &&
|
76
|
+
ruleset.declarations.find { |dec| dec.property.text == 'color' }
|
77
|
+
['禁止重写reset中定义的a标签的hover色(现为#ff7300)', :error]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
check_declarations_sequence do |ruleset|
|
82
|
+
list = [
|
83
|
+
%w(position display visible z-index overflow float clear),
|
84
|
+
%w(width height top right bottom left margin padding border),
|
85
|
+
%w(background opacity),
|
86
|
+
%w(font color text line-height vertical-align)
|
87
|
+
]
|
88
|
+
|
89
|
+
now = 0
|
90
|
+
ruleset.declarations.each do |dec|
|
91
|
+
prop_text = dec.property.text
|
92
|
+
index = list.find_index do |bag|
|
93
|
+
bag.any? { |field| prop_text.index field }
|
94
|
+
end
|
95
|
+
|
96
|
+
next unless index
|
97
|
+
return ['建议使用Mozilla推荐CSS书写顺序'\
|
98
|
+
'http://wd.alibaba-inc.com/doc/page/regulations/css', :warn] if index < now
|
99
|
+
now = index
|
100
|
+
end
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
check_selector_using_hack do |selector|
|
105
|
+
if selector =~ /\S\*/
|
106
|
+
['合理使用hack', :error]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
check_property_hack do |property|
|
111
|
+
if property =~ /[^-a-z]/
|
112
|
+
['合理使用hack', :error]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
check_value_use_css_expression do |value|
|
117
|
+
if value =~ /^expression\(/
|
118
|
+
['禁止使用CSS表达式', :error]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
check_value_use_hack do |value|
|
123
|
+
if value =~ /\\\d$/
|
124
|
+
['合理使用hack', :error]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vi: filetype=ruby
|
3
|
+
html
|
4
|
+
|
5
|
+
check_doc do |doc|
|
6
|
+
unless context.have_dtd?
|
7
|
+
["必须存在文档类型声明", :error]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
check_dtd_type_must_be_html5 do |dtd|
|
12
|
+
unless dtd.type =~ /^html$/i
|
13
|
+
['新页面统一使用HTML 5 DTD', :warn]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
check_dtd_upcase do |dtd|
|
18
|
+
unless dtd =~ /^<!DOCTYPE/
|
19
|
+
['必须使用大写的"DOCTYPE"', :warn]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vi: filetype=ruby
|
3
|
+
|
4
|
+
html
|
5
|
+
|
6
|
+
check_property_no_inline_style do |prop|
|
7
|
+
if prop.name =~ /^style$/im
|
8
|
+
["不能定义内嵌样式style", :error]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
check_property_name_downcase do |prop|
|
13
|
+
if prop.name =~ /[A-Z]/
|
14
|
+
["属性名必须小写,连字符用中横线", :error]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
check_property_id_prop_value_downcase do |prop|
|
19
|
+
if prop.name_equal? 'id' and prop.value =~ /[A-Z]/
|
20
|
+
["id名称全部小写,单词分隔使用中横线", :error]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
check_property_class_prop_value_downcase do |prop|
|
25
|
+
if prop.name_equal? 'class' and prop.value =~ /[A-Z]/
|
26
|
+
["class名称全部小写,单词分隔使用中横线", :error]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
check_property_value_sep do |prop|
|
31
|
+
if prop.value and prop.sep != '"'
|
32
|
+
["属性值必须使用双引号", :error]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
check_property_value_exsit do |prop|
|
37
|
+
if prop.value.nil?
|
38
|
+
["不能仅有属性名", :error]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
check_property_class_count do |prop|
|
43
|
+
if prop.name_equal? 'class'
|
44
|
+
names = prop.value.split /\s+/
|
45
|
+
names.reject! { |s| s =~ /^(fd-|layout|grid|w952)\b/ }
|
46
|
+
if names.size > 3
|
47
|
+
["一个节点上定义的class个数最多不超过3个(不含lib中的class)", :error]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vi: filetype=ruby
|
3
|
+
|
4
|
+
html
|
5
|
+
|
6
|
+
check_tag_name_downcase do |tag|
|
7
|
+
if tag.tag_name =~ /[A-Z]/ || tag.ending =~ /[A-Z]/
|
8
|
+
["标签名必须小写", :error]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
check_tag_img_must_have_alt do |tag|
|
13
|
+
if tag.tag_name_equal? 'img'
|
14
|
+
unless tag.has_prop?(:alt)
|
15
|
+
["img标签必须加上alt属性", :error]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
check_tag_hyperlink_with_target do |tag|
|
21
|
+
|
22
|
+
if tag.tag_name_equal? 'base' and tag.prop_value(:target)
|
23
|
+
@has_target = true
|
24
|
+
end
|
25
|
+
|
26
|
+
unless @has_target
|
27
|
+
if tag.tag_name_equal? 'a' and tag.prop_value(:href) =~ /^#/ and tag.prop_value(:target) != '_self'
|
28
|
+
['功能a必须加target="_self",除非preventDefault过', :warn]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
check_tag_hyperlink_with_title do |tag|
|
34
|
+
if tag.tag_name_equal? 'a' and tag.prop_value(:href) =~ /^[^#]/
|
35
|
+
unless (prop = tag.prop_value(:title)) and !prop.empty?
|
36
|
+
['非功能能点的a标签必须加上title属性', :error]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
check_tag_unique_script_import do |tag|
|
42
|
+
if tag.tag_name_equal? 'script'
|
43
|
+
src = tag.prop_value(:src).to_s
|
44
|
+
if !src.empty? and context.script_used? src
|
45
|
+
["避免重复引用同一或相同功能文件", :error]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
check_tag_unique_style_import do |tag|
|
51
|
+
if tag.tag_name_equal? 'link' and tag.prop_value(:rel) =~ /stylesheet/i
|
52
|
+
src = tag.prop_value(:href).to_s
|
53
|
+
if !src.empty? and context.style_used? src
|
54
|
+
["避免重复引用同一或相同功能文件", :error]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
check_tag_no_import_in_style_tag do |tag|
|
60
|
+
if tag.tag_name_equal? 'style' and tag.inner_text =~ /@import\s/m
|
61
|
+
["不通过@import在页面上引入CSS", :error]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
check_tag_head_contain_meta_and_title do |tag|
|
66
|
+
if tag.tag_name_equal? 'head'
|
67
|
+
children = tag.children
|
68
|
+
has_meta = children.any? { |e| e.tag_name_equal? 'meta' and e.prop_value('charset') }
|
69
|
+
has_title = children.any? { |e| e.tag_name_equal? 'title' }
|
70
|
+
unless has_meta and has_title
|
71
|
+
["head必须包含字符集meta和title", :error]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
check_tag_block_in_block do |tag|
|
77
|
+
if !tag.tag_name_equal?('a') and tag.inline? and tag.children.any? { |e| !e.inline? }
|
78
|
+
["行内标签不得包含块级标签,a标签例外", :error]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
check_tag_form_element_with_name do |tag|
|
83
|
+
if (tag.tag_name_equal?('input') and %w(text radio checkbox).include?(tag.prop_value('type').to_s.downcase) or
|
84
|
+
tag.tag_name_equal?('select') or
|
85
|
+
tag.tag_name_equal?('textarea'))
|
86
|
+
val = tag.prop_value('name')
|
87
|
+
unless val and !val.empty?
|
88
|
+
["text、radio、checkbox、textarea、select必须加name属性", :error]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
check_tag_form_button do |tag|
|
94
|
+
if tag.tag_name_equal?('input') and %w(button submit reset).include?(tag.prop_value('type').to_s.downcase)
|
95
|
+
["所有按钮必须用button(button/submit/reset)", :error]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
check_tag_css_in_head do |tag|
|
100
|
+
if tag.tag_name_equal?('link') and tag.prop_value(:rel) =~ /stylesheet/i
|
101
|
+
if tag.has_scope? and !tag.in_scope?('head')
|
102
|
+
["外链CSS置于head里(例外:应用里的footer样式)", :warn]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
check_tag_closed do |tag|
|
108
|
+
if !tag.closed? or (tag.auto_close? and !tag.self_closed?)
|
109
|
+
["标签必须正确闭合", :error]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
check_tag_html_template do |tag|
|
114
|
+
info = ["新页面按库中的HTML基本结构模板书写基本页面结构", :warn]
|
115
|
+
if tag.tag_name_equal?('head')
|
116
|
+
if !tag.children.any? { |e| e === 'meta[name==description]' } or
|
117
|
+
!tag.children.any? { |e| e === 'meta[name==keywords]' }
|
118
|
+
info
|
119
|
+
end
|
120
|
+
elsif tag.tag_name_equal?('body')
|
121
|
+
info unless tag.children.any? { |e| e === 'div#doc' }
|
122
|
+
elsif tag === 'div#doc'
|
123
|
+
if !tag.children.any? { |e| e === 'div#header' } or
|
124
|
+
!tag.children.any? { |e| e === 'div#footer' } or
|
125
|
+
!tag.children.any? { |e| e === 'div#alibar' } or
|
126
|
+
!tag.children.any? { |e| e === 'div#content' }
|
127
|
+
info
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
check_text_should_escape do |el|
|
133
|
+
if el.text =~ /[<>]/
|
134
|
+
["特殊HTML符号(>和<)必须转义", :error]
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vi: filetype=ruby
|
3
|
+
|
4
|
+
js
|
5
|
+
|
6
|
+
JQ_IDS = %w(jQuery $ jQ jq)
|
7
|
+
|
8
|
+
check_expr_member_direct_jquery_call do |expr|
|
9
|
+
expr = context.find_expr_member(expr) do |expr|
|
10
|
+
['.', '(', '['].include?(expr.type) && expr.left.text == 'jQuery'
|
11
|
+
end
|
12
|
+
|
13
|
+
if expr
|
14
|
+
unless expr.type == '.' && expr.right.text == 'namespace'
|
15
|
+
['禁止直接使用jQuery变量,使用全局闭包写法"(function($, NS){....})(jQuery,Namespace);",jQuery.namespace例外', :error]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
check_expr_member_forbit_method_call do |expr|
|
21
|
+
methods = %w(sub noConflict)
|
22
|
+
expr = context.find_expr_member(expr) do |expr|
|
23
|
+
expr.type == '.' && JQ_IDS.include?(expr.left.text) &&
|
24
|
+
methods.include?(expr.right.text)
|
25
|
+
end
|
26
|
+
|
27
|
+
['禁止使用jQuery.sub()和jQuery.noConflict方法', :error] if expr
|
28
|
+
end
|
29
|
+
|
30
|
+
check_expr_member_data_call_param do |expr|
|
31
|
+
expr = context.find_expr_member(expr) do |expr|
|
32
|
+
if expr.type == '('
|
33
|
+
name = expr.left
|
34
|
+
if name.is_a?(XRay::JS::Expression) && name.type == '.' &&
|
35
|
+
name.right.text == 'data'
|
36
|
+
param = expr.right[0]
|
37
|
+
param && param.text =~ /[-_]/
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
['使用".data()"读写自定义属性时需要转化成驼峰形式', :error] if expr
|
43
|
+
end
|
44
|
+
|
45
|
+
check_expr_member_ctor_selector do |expr|
|
46
|
+
expr = context.find_expr_member(expr) do |expr|
|
47
|
+
expr.type == '(' && JQ_IDS.include?(expr.left.text)
|
48
|
+
end
|
49
|
+
|
50
|
+
if expr
|
51
|
+
param = expr.right[0]
|
52
|
+
if param && param.type == 'string' && param.text !~ /^['"][#\w<]/
|
53
|
+
['使用选择器时,能确定tagName的,必须加上tagName', :warn]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# vi: filetype=ruby
|
3
|
+
|
4
|
+
js
|
5
|
+
|
6
|
+
check_merge_importing_one_line_one_import do |import, pathes, file|
|
7
|
+
same_line = pathes.select { |path| path[:row] == import[:row] }
|
8
|
+
if same_line.size > 1 and same_line.index(import) > 0
|
9
|
+
['一行只能有一个import文件', :error, import[:row], import[:col]]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
check_merge_importing_strict_import_js do |import, pathes, file|
|
14
|
+
url = import[:url]
|
15
|
+
if context.lib_scope?(url) or context.relative?(url)
|
16
|
+
break
|
17
|
+
end
|
18
|
+
|
19
|
+
unless /ImportJavscript\.url\(['"]?([^'"]+)['"]?\);/ =~ import[:text]
|
20
|
+
['merge文件格式不正确', :error, import[:row], import[:col]]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
check_merge_importing_global_cannot_import_page do |import, pathes, file|
|
25
|
+
url = import[:url]
|
26
|
+
if context.lib_scope?(url) or context.relative?(url)
|
27
|
+
break
|
28
|
+
end
|
29
|
+
|
30
|
+
if context.global_scope? file and context.page_scope?(url)
|
31
|
+
['产品线merge文件不能引用page文件', :error, import[:row], import[:col]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
check_merge_importing_can_only_import_from_same_app do |import, pathes, file|
|
36
|
+
url = import[:url]
|
37
|
+
if context.lib_scope?(url) or context.relative?(url)
|
38
|
+
break
|
39
|
+
end
|
40
|
+
|
41
|
+
app = context.app(file)
|
42
|
+
if app && app != context.app(url)
|
43
|
+
['merge文件引用非该产品线文件', :error, import[:row], import[:col]]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
check_merge_importing_page_can_only_import_same_page do |import, pathes, file|
|
48
|
+
if context.page_scope?(file)
|
49
|
+
path = file.sub /\\/, '/'
|
50
|
+
url = import[:url]
|
51
|
+
prefix = File.basename(path).sub /-merge([-_]\d+)?\.js/, ''
|
52
|
+
page_pattern = %r"/page/#{prefix}(-min|(/.+))?\.js"
|
53
|
+
if context.page_scope?(file) && context.page_scope?(url) && url !~ page_pattern
|
54
|
+
['页面级别merge文件只允许merge当前页面所属js文件', :error, import[:row], import[:col]]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##Style 拆分之后可能merge的文件已经不存在
|
60
|
+
##stop checking this rule
|
61
|
+
#check_merge_importing_exist do |import, pathes, file|
|
62
|
+
#end
|
63
|
+
|
64
|
+
check_merge_importing_must_be_minified do |import, pathes, file|
|
65
|
+
if !context.min_file?(import[:url])
|
66
|
+
['merge文件需要引用压缩版的js, 如a-min.js', :warn,
|
67
|
+
import[:row],
|
68
|
+
import[:col] + import[:text].rindex(/\/|\\/) + 1
|
69
|
+
]
|
70
|
+
end
|
71
|
+
end
|