├── ckstyle ├── __init__.py ├── browsers │ ├── __init__.py │ ├── Detector.py │ ├── BinaryRule.py │ └── Analyser.py ├── command │ ├── __init__.py │ ├── index.py │ ├── args.py │ └── PluginManager.py ├── entity │ ├── __init__.py │ ├── NestedStatement.py │ ├── ExtraStatement.py │ ├── Rule.py │ ├── EntityUtil.py │ └── StyleSheet.py ├── plugins │ ├── __init__.py │ ├── combiners │ │ ├── __init__.py │ │ ├── Combiner.py │ │ ├── helper.py │ │ └── CombinerFactory.py │ ├── validators │ │ ├── __init__.py │ │ ├── MarginValidator.py │ │ └── ValidatorFactory.py │ ├── FEDUnknownCssNameChecker.py │ ├── FEDDistinguishBrowserRule.py │ ├── FEDDistinguishBrowserRuleSet.py │ ├── FEDSemicolonAfterValue.py │ ├── FEDFixOutlineZero.py │ ├── FEDShouldNotUseImportant.py │ ├── FEDSelectorNoUnderLine.py │ ├── FEDDistinguishBrowserExtra.py │ ├── FEDNoEmptyRuleSet.py │ ├── FEDNoAlphaImageLoader.py │ ├── FEDCommentLengthLessThan80.py │ ├── FEDUseSingleQuotation.py │ ├── FEDUseValidValues.py │ ├── FEDNoCommentInValues.py │ ├── FEDTransChnFontFamilyNameIntoEng.py │ ├── FEDDoNotSetStyleForTagOnly.py │ ├── FEDCanNotSetFontFamily.py │ ├── FEDUseLowerCaseSelector.py │ ├── FEDHackAttributeInCorrectWay.py │ ├── FEDZIndexShouldInRange.py │ ├── FEDFixCommentInValue.py │ ├── FEDMustContainAuthorInfo.py │ ├── FEDReplaceBorderZeroWithBorderNone.py │ ├── FEDNoAppearanceNameInSelector.py │ ├── FEDNoStarInSelector.py │ ├── FEDNoSimpleNumberInSelector.py │ ├── Base.py │ ├── FEDFixNestedStatement.py │ ├── FEDSingleLineSelector.py │ ├── FEDDoNotSetStyleForSimpleSelector.py │ ├── FEDNoZeroBeforeDot.py │ ├── FEDFontSizeShouldBePtOrPx.py │ ├── FEDHackRuleSetInCorrectWay.py │ ├── FEDNoExpression.py │ ├── FEDSingleLineSpaces.py │ ├── FEDSingleLineBraces.py │ ├── FEDMultiLineBraces.py │ ├── FEDUseLowerCaseProp.py │ ├── FEDRemoveDuplicatedAttr.py │ ├── FEDStyleShouldInOrder.py │ ├── FEDUnknownHTMLTagName.py │ ├── FEDNoUnitAfterZero.py │ └── FEDMultiLineSpaces.py ├── reporter │ ├── __init__.py │ ├── Reporter.py │ ├── XMLReporter.py │ ├── ReporterUtil.py │ ├── JsonReporter.py │ ├── TextReporter.py │ └── helper.py ├── cmdconsole │ ├── __init__.py │ └── ConsoleClass.py ├── cssparser │ ├── __init__.py │ └── helper.py └── userplugins │ ├── __init__.py │ ├── commands │ └── __init__.py │ └── plugins │ └── __init__.py ├── tests ├── unit │ ├── __init__.py │ ├── check │ │ ├── __init__.py │ │ ├── FEDNoEmptyFile.css │ │ ├── FEDNoAlphaImageLoader.css │ │ ├── FEDSelectorNoUnderLine.css │ │ ├── FEDUseSingleQuotation.css │ │ ├── FEDNoZeroBeforeDot.css │ │ ├── FEDReplaceBorderZeroWithBorderNone.css │ │ ├── FEDMustContainAuthorInfoAnotherOK.css │ │ ├── FEDNoEmptyRuleSet.css │ │ ├── FEDNoStarInSelector.css │ │ ├── FEDSemicolonAfterValue.css │ │ ├── helper.py │ │ ├── FEDUnknownCssNameChecker.css │ │ ├── demo.py │ │ ├── FEDMustContainAuthorInfo.css │ │ ├── FEDMustContainAuthorInfoAnother.css │ │ ├── FEDShouldNotUseImportant.css │ │ ├── FEDNoAppearanceNameInSelector.css │ │ ├── FEDNoEmptyFile.py │ │ ├── FEDTransChnFontFamilyNameIntoEng.css │ │ ├── FED16ColorShouldUpper.css │ │ ├── FEDDoNotSetStyleForTagOnly.css │ │ ├── FEDUseLowerCaseSelector.css │ │ ├── FEDCanNotSetFontFamily.css │ │ ├── FEDDoNotSetStyleForSimpleSelector.css │ │ ├── FEDHackAttributeInCorrectWay.css │ │ ├── FEDSingleLineSelector.css │ │ ├── FEDZIndexShouldInRange.css │ │ ├── FEDNoUnitAfterZero.css │ │ ├── FEDFontSizeShouldBePtOrPx.css │ │ ├── FEDHackRuleSetInCorrectWay.css │ │ ├── FEDNoSimpleNumberInSelector.css │ │ ├── FEDUseLowerCaseProp.css │ │ ├── FEDMultiLineBraces.css │ │ ├── FEDSingleLineSpaces.css │ │ ├── FEDCombineInToOne.css │ │ ├── FEDCommentLengthLessThan80.css │ │ ├── FEDNoExpression.css │ │ ├── FEDMultiLineSpaces.css │ │ ├── FEDMultiLineSelectors.css │ │ ├── FEDStyleShouldInOrder.css │ │ ├── FEDSingleLineBraces.css │ │ └── FEDHighPerformanceSelector.css │ ├── fix │ │ ├── __init__.py │ │ ├── FixFEDOutlineNone.py │ │ ├── FixFEDCss3PropSpaces.py │ │ ├── FixFEDNoEmptyRuleSet.py │ │ ├── helper.py │ │ ├── FixPropStartWithSharp.py │ │ ├── Try.py │ │ ├── FixRuleSetsFormat.py │ │ ├── FixFEDCombineInToOne.py │ │ ├── CommentForExtraStatement.py │ │ ├── FixAllInSafeMode.py │ │ ├── FixFEDUseLowerCaseSelector.py │ │ ├── ExtraCss3Prefix.py │ │ ├── FixFEDNoUnitAfterZero.py │ │ ├── SoManyColors.py │ │ ├── FEDCombineSameRuleSets.py │ │ └── FixComplicatedStatement.py │ ├── hacks │ │ ├── __init__.py │ │ ├── helper.py │ │ ├── HackRuleSets.py │ │ ├── HackExtras.py │ │ └── HackRules.py │ ├── order │ │ ├── __init__.py │ │ ├── helper.py │ │ ├── Try.py │ │ └── InAMessOrder.py │ ├── todos │ │ ├── __init__.py │ │ ├── TryParseKeyFrames.py │ │ └── helper.py │ ├── combiner │ │ ├── __init__.py │ │ ├── _just_margin.css │ │ ├── _just_padding.css │ │ ├── _test.css │ │ ├── helper.py │ │ ├── _test_different_order.css │ │ ├── _with_margin.css │ │ ├── _with_padding.css │ │ └── Try.py │ ├── compress │ │ ├── __init__.py │ │ ├── _browsers_importanter_hacks.css │ │ ├── _expression.css │ │ ├── _one_line_file.css │ │ ├── _extra_statement.css │ │ ├── _browsers.css │ │ ├── _compress_special_hack_chars.css │ │ ├── _with_extra.css │ │ ├── _browsers_combine_ruleset.css │ │ ├── _file.css │ │ ├── CompressNestedStatement.py │ │ ├── CompressCssCompiler.py │ │ ├── helper.py │ │ ├── _selectors_from_kimblim.css │ │ ├── CompressWithOrder.py │ │ └── CompressFile.py │ ├── config │ │ ├── __init__.py │ │ ├── ckstyle_missing.ini │ │ ├── ckstyle_with_plugin.ini │ │ ├── ckstyle_configed.ini │ │ ├── ckstyle.ini │ │ ├── helper.py │ │ └── WithPlugin.py │ ├── entity │ │ ├── __init__.py │ │ ├── helper.py │ │ ├── StyleSheet.py │ │ ├── Rule.py │ │ ├── ExtraStatement.py │ │ └── RuleSet.py │ ├── parser │ │ ├── __init__.py │ │ ├── helper.py │ │ ├── ParserIgnoreSpeicalStatement.css │ │ ├── NestedStatement.py │ │ └── ExtraStatement.py │ ├── tellme │ │ └── __init__.py │ ├── commandline │ │ ├── __init__.py │ │ ├── _test.css │ │ ├── _test_browsers.css │ │ ├── helper.py │ │ └── Try.py │ └── asserts.py ├── experiment │ ├── test.css │ ├── outer │ │ ├── outer.css │ │ └── inner │ │ │ ├── inner.css │ │ │ └── inner2.css │ ├── go.bat │ ├── python.py │ ├── xxx.ini │ └── test.py ├── go.bat ├── demo │ ├── demo-compress.min.css │ ├── demo-ckstyle-step1.css │ ├── demo-ckstyle-step2.css │ ├── demo-ckstyle-step3.css │ └── demo-compress.css └── test.css ├── go.bat ├── utils ├── go.bat ├── python_gflags-2.0-py3.1.egg.rar ├── test.py ├── combineRules.py └── exportRules.py ├── bin ├── ckstyle-admin.py ├── compress-admin.py ├── fixstyle-admin.py ├── ckstyle.bat ├── compress.bat └── fixstyle.bat ├── editor-plugins ├── fixstyle for EditPlus.mkd ├── fixstyle for notepad++.mkd ├── fixstyle_for_sublime_text_2.zip ├── fixstyle_for_sublime_text_2 │ ├── Default (OSX).sublime-keymap │ ├── Default (Linux).sublime-keymap │ ├── Default (Windows).sublime-keymap │ ├── Preferences.sublime-settings │ ├── helper.py │ ├── Context.sublime-menu │ ├── README.md │ ├── CkStylePlugin.py │ ├── Main.sublime-menu │ ├── FixStylePlugin.py │ ├── CssCompressPlugin.py │ ├── FixStyleSafePlugin.py │ └── FixStyleSingleLinePlugin.py └── fixstyle.vim ├── doc └── index.html ├── .gitignore ├── ckstyle.ini └── LICENSE /ckstyle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/browsers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/command/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/entity/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/reporter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/experiment/test.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/check/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/fix/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/hacks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/todos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/cmdconsole/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/cssparser/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/userplugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/combiner/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/compress/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/entity/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/parser/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/tellme/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/plugins/combiners/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/plugins/validators/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/commandline/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/userplugins/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ckstyle/userplugins/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.bat: -------------------------------------------------------------------------------- 1 | python setup.py install 2 | python tests/runUnitTests.py 3 | -------------------------------------------------------------------------------- /tests/unit/commandline/_test.css: -------------------------------------------------------------------------------- 1 | .test { 2 | width: 100px; 3 | } -------------------------------------------------------------------------------- /tests/experiment/outer/outer.css: -------------------------------------------------------------------------------- 1 | fdsa { 2 | width:100px; 3 | } 4 | -------------------------------------------------------------------------------- /tests/go.bat: -------------------------------------------------------------------------------- 1 | python ../setup.py install 2 | python runUnitTests.py 3 | -------------------------------------------------------------------------------- /tests/experiment/outer/inner/inner.css: -------------------------------------------------------------------------------- 1 | width { 2 | width: 100px; 3 | } 4 | -------------------------------------------------------------------------------- /tests/experiment/outer/inner/inner2.css: -------------------------------------------------------------------------------- 1 | width { 2 | width: 100px; 3 | } 4 | -------------------------------------------------------------------------------- /tests/experiment/go.bat: -------------------------------------------------------------------------------- 1 | python ../../setup.py install 2 | python ../runUnitTests.py 3 | -------------------------------------------------------------------------------- /tests/unit/todos/TryParseKeyFrames.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | pass 5 | -------------------------------------------------------------------------------- /utils/go.bat: -------------------------------------------------------------------------------- 1 | python ../setup.py install 2 | python ../tests/runUnitTests.py 3 | python .\exportRules.py 4 | -------------------------------------------------------------------------------- /ckstyle/plugins/combiners/Combiner.py: -------------------------------------------------------------------------------- 1 | class Combiner(): 2 | def combine(self, name, attrs): 3 | pass 4 | -------------------------------------------------------------------------------- /bin/ckstyle-admin.py: -------------------------------------------------------------------------------- 1 | from ckstyle.command.index import ckstyle 2 | 3 | if __name__ == '__main__': 4 | ckstyle() 5 | -------------------------------------------------------------------------------- /bin/compress-admin.py: -------------------------------------------------------------------------------- 1 | from ckstyle.command.index import compress 2 | 3 | if __name__ == '__main__': 4 | compress() 5 | -------------------------------------------------------------------------------- /bin/fixstyle-admin.py: -------------------------------------------------------------------------------- 1 | from ckstyle.command.index import fixstyle 2 | 3 | if __name__ == '__main__': 4 | fixstyle() 5 | -------------------------------------------------------------------------------- /tests/unit/commandline/_test_browsers.css: -------------------------------------------------------------------------------- 1 | .test { 2 | width: 100px; 3 | } 4 | 5 | 6 | .test[prop] { 7 | width: 100px; 8 | } -------------------------------------------------------------------------------- /utils/python_gflags-2.0-py3.1.egg.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjeaf/CSSCheckStyle/HEAD/utils/python_gflags-2.0-py3.1.egg.rar -------------------------------------------------------------------------------- /editor-plugins/fixstyle for EditPlus.mkd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjeaf/CSSCheckStyle/HEAD/editor-plugins/fixstyle for EditPlus.mkd -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 请查看README,或移步 https://github.com/wangjeaf/CSSCheckStyle-docs 2 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle for notepad++.mkd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjeaf/CSSCheckStyle/HEAD/editor-plugins/fixstyle for notepad++.mkd -------------------------------------------------------------------------------- /ckstyle/plugins/combiners/helper.py: -------------------------------------------------------------------------------- 1 | def containsHack(name, strippedName, value): 2 | return name != strippedName or value.find('\9') != -1 3 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangjeaf/CSSCheckStyle/HEAD/editor-plugins/fixstyle_for_sublime_text_2.zip -------------------------------------------------------------------------------- /tests/unit/compress/_browsers_importanter_hacks.css: -------------------------------------------------------------------------------- 1 | .test[fd*=df], .test:not(xxx) { 2 | width:100px; 3 | } 4 | 5 | .test[fd~=df] { 6 | width:100px; 7 | } -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+f1"], "command": "fixstyle" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+f1"], "command": "fixstyle" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+f1"], "command": "fixstyle" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /tests/experiment/python.py: -------------------------------------------------------------------------------- 1 | import os 2 | homedir = os.getenv('USERPROFILE') or os.getenv('HOME') 3 | print os.path.realpath(os.path.join(homedir, 'ckstyle.ini')) 4 | -------------------------------------------------------------------------------- /tests/demo/demo-compress.min.css: -------------------------------------------------------------------------------- 1 | .test1,.test2,.test3,.test4,.test5{*display:none;_display:inline-block;width:100px;height:200px;margin:20px 10px 10px;border:1px solid #FFF} 2 | -------------------------------------------------------------------------------- /tests/unit/config/ckstyle_missing.ini: -------------------------------------------------------------------------------- 1 | [ckstyle] 2 | tab-spaces = 2 3 | standard = standard3.css 4 | fixed-extension = .fixed2.css 5 | 6 | [compress] 7 | extension = .lala.min.css 8 | -------------------------------------------------------------------------------- /utils/test.py: -------------------------------------------------------------------------------- 1 | plugin = __import__("ckstyle.plugins.AllRules", fromlist = ['AllRules']) 2 | props = dir(plugin) 3 | for prop in props: 4 | if prop.startswith('FED'): 5 | print prop -------------------------------------------------------------------------------- /tests/test.css: -------------------------------------------------------------------------------- 1 | .test { margin-left: 1px; margin-right: 1px; margin-top: 0px; margin-bottom: 0; padding-top: 0px; padding-bottom: 0; padding-right: 0; padding-left: 0; } -------------------------------------------------------------------------------- /tests/demo/demo-ckstyle-step1.css: -------------------------------------------------------------------------------- 1 | /* @author : zhifu.wang */ 2 | 3 | .test1 ul li a { 4 | width:10px; 5 | color:#ffffff; 6 | -webkit-border-radius:3px; 7 | -moz-border-radius : 3px; 8 | border-radius:3px 9 | } 10 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/Preferences.sublime-settings: -------------------------------------------------------------------------------- 1 | // Settings in here override those in "Default/Preferences.sublime-settings", and 2 | // are overridden in turn by file type specific settings. 3 | { 4 | } 5 | -------------------------------------------------------------------------------- /ckstyle/reporter/Reporter.py: -------------------------------------------------------------------------------- 1 | class Reporter(): 2 | def __init__(self, checker): 3 | pass 4 | def doReport(self): 5 | pass 6 | def appendMsg(self): 7 | pass 8 | def export(self): 9 | pass 10 | -------------------------------------------------------------------------------- /tests/unit/compress/_expression.css: -------------------------------------------------------------------------------- 1 | *html .feed-comment textarea { 2 | behavior: expression(function(ele){ 3 | ele.runtimeStyle.behavior='none'; 4 | Expressions.pseudo.hover(ele, 'textarea_hover'); 5 | }(this)); 6 | } 7 | -------------------------------------------------------------------------------- /tests/unit/compress/_one_line_file.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zhifu.wang 3 | */ 4 | 5 | .test { width: 100px; height: 200px; _z-index: 111; } @keyframes 'name' { 10% {width: 100px;} } .another { *width: 100px; color: #DDDDDD; background-color: #aabbcc; } 6 | -------------------------------------------------------------------------------- /tests/unit/fix/FixFEDOutlineNone.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _try() 5 | 6 | def _try(): 7 | fixer, msg = doFix('.test {outline:none;}', '') 8 | equal(msg, '''.test { 9 | outline: 0; 10 | }''', 'outline fix ok') 11 | -------------------------------------------------------------------------------- /bin/ckstyle.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Use a full path to Python (relative to this script) as the standard Python 4 | rem install does not put python.exe on the PATH... 5 | rem %~dp0 is the directory of this script 6 | 7 | "%~dp0..\python" "%~dp0ckstyle-admin.py" %* 8 | -------------------------------------------------------------------------------- /bin/compress.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Use a full path to Python (relative to this script) as the standard Python 4 | rem install does not put python.exe on the PATH... 5 | rem %~dp0 is the directory of this script 6 | 7 | "%~dp0..\python" "%~dp0compress-admin.py" %* 8 | -------------------------------------------------------------------------------- /bin/fixstyle.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Use a full path to Python (relative to this script) as the standard Python 4 | rem install does not put python.exe on the PATH... 5 | rem %~dp0 is the directory of this script 6 | 7 | "%~dp0..\python" "%~dp0fixstyle-admin.py" %* 8 | -------------------------------------------------------------------------------- /tests/experiment/xxx.ini: -------------------------------------------------------------------------------- 1 | [config] 2 | error-level = 0 3 | include = all 4 | exclude = none 5 | recursive = false 6 | print-flag = false 7 | extension = .ckstyle2.txt 8 | tab-spaces = 4 9 | standard = standard.css 10 | ignore-rule-sets = @unit-test-expecteds 11 | -------------------------------------------------------------------------------- /tests/unit/config/ckstyle_with_plugin.ini: -------------------------------------------------------------------------------- 1 | [ckstyle] 2 | tab-spaces = 2 3 | standard = standard3.css 4 | fixed-extension = .fixed2.css 5 | 6 | [compress] 7 | extension = .lala.min.css 8 | 9 | [plugin] 10 | plugin-a-config = 1 11 | plugin-b-config = 2 12 | pluginCConfig = 3 -------------------------------------------------------------------------------- /tests/unit/order/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.doCssFix import doFix 9 | -------------------------------------------------------------------------------- /tests/unit/todos/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.doCssFix import doFix 9 | -------------------------------------------------------------------------------- /tests/unit/compress/_extra_statement.css: -------------------------------------------------------------------------------- 1 | @-css-compiler { 2 | selector-compile: no-combinator; 3 | rule-compile: all; 4 | } 5 | 6 | @charset utf-8; 7 | 8 | @-css-compiler-xxx fdasfdas; 9 | 10 | @import url(fdafdas/fdafdas.css) 11 | -------------------------------------------------------------------------------- /tests/demo/demo-ckstyle-step2.css: -------------------------------------------------------------------------------- 1 | /* @author : zhifu.wang */ 2 | 3 | .test-special-word a.a-class { 4 | width:10px; 5 | color:#FFFFFF; 6 | -webkit-border-radius: 3px; 7 | -moz-border-radius: 3px; 8 | -o-border-radius: 3px; 9 | border-radius: 3px; 10 | } 11 | -------------------------------------------------------------------------------- /tests/demo/demo-ckstyle-step3.css: -------------------------------------------------------------------------------- 1 | /* @author : zhifu.wang */ 2 | 3 | .test-special-word .a-class { 4 | width: 10px; 5 | color: #FFF; 6 | -webkit-border-radius: 3px; 7 | -moz-border-radius: 3px; 8 | -o-border-radius: 3px; 9 | border-radius: 3px; 10 | } 11 | -------------------------------------------------------------------------------- /tests/unit/parser/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.cssparser.CssFileParser import CssParser 9 | -------------------------------------------------------------------------------- /tests/unit/compress/_browsers.css: -------------------------------------------------------------------------------- 1 | *html a { 2 | width: 100px; 3 | } 4 | 5 | *+html a { 6 | width: 200px; 7 | } 8 | 9 | a { 10 | -webkit-transform: 1s; 11 | -moz-transform: 1s; 12 | -o-transform: 1s; 13 | } 14 | 15 | b { 16 | -moz-transform: 1s; 17 | width: 300px; 18 | _width: 400px; 19 | } -------------------------------------------------------------------------------- /tests/unit/compress/_compress_special_hack_chars.css: -------------------------------------------------------------------------------- 1 | li:nth-child(even) { 2 | background:gray; 3 | } 4 | * html li.even { 5 | background:gray; 6 | } 7 | .test[^=aaa] { 8 | background:gray; 9 | } 10 | 11 | .test1 { 12 | width: 100px; 13 | } 14 | .test2 { 15 | width: 100px; 16 | } 17 | -------------------------------------------------------------------------------- /ckstyle/reporter/XMLReporter.py: -------------------------------------------------------------------------------- 1 | from .Reporter import Reporter 2 | 3 | class XMLReporter(Reporter): 4 | def __init__(self, checker): 5 | pass 6 | 7 | def doReport(self): 8 | pass 9 | 10 | def appendMsg(self, msg): 11 | pass 12 | 13 | def export(self): 14 | pass 15 | -------------------------------------------------------------------------------- /tests/unit/compress/_with_extra.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zhifu.wang 3 | */ 4 | 5 | @charset utf-8; 6 | 7 | @import url('xxxxx'); 8 | @namespace lalala; 9 | 10 | .test { 11 | width: 100px; 12 | height: 200px; 13 | _z-index: 111; 14 | } 15 | 16 | @import url('xxx2'); 17 | 18 | @import url('xxx3'); 19 | -------------------------------------------------------------------------------- /tests/unit/compress/_browsers_combine_ruleset.css: -------------------------------------------------------------------------------- 1 | a { 2 | -webkit-transform: 1s; 3 | -moz-transform: 1s; 4 | -o-transform: 1s; 5 | width: 300px; 6 | } 7 | 8 | b { 9 | -moz-transform: 1s; 10 | width: 300px; 11 | _width: 400px; 12 | } 13 | 14 | d { 15 | -moz-transform: 1s; 16 | width: 300px; 17 | _width: 400px; 18 | } -------------------------------------------------------------------------------- /tests/unit/hacks/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.browsers.BinaryRule import * 9 | from ckstyle.browsers.Hacks import * 10 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoEmptyFile.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @author : zhifu.wang@renren-inc.com 3 | */ 4 | 5 | /* because this is an ruleSet, so the error will not occur..., by python! */ 6 | @unit-test-expecteds { 7 | } 8 | 9 | /* comment 1 */ 10 | /* comment 2 */ 11 | /* 12 | .test-b { width: 100px; 13 | } 14 | 15 | .test-d { 16 | width: 100px; 17 | }*/ 18 | 19 | -------------------------------------------------------------------------------- /tests/unit/compress/_file.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zhifu.wang 3 | */ 4 | 5 | .test { 6 | width: 100px; 7 | height: 200px; 8 | _z-index: 111; 9 | } 10 | 11 | @keyframes 'name' { 12 | 10% { 13 | width: 100px; 14 | } 15 | } 16 | 17 | .another { 18 | *width: 100px; 19 | color: #DDDDDD; 20 | background-color: #aabbcc; 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit/compress/CompressNestedStatement.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _go() 5 | 6 | def _go(): 7 | msg = doCssCompress('@media print{/* Hide the cursor when printing */.CodeMirror div.CodeMirror-cursor{visibility:hidden}}') 8 | equal(msg, '@media print{.CodeMirror div.CodeMirror-cursor{visibility:hidden}}', 'nested statement compress is ok') 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.swp 3 | *.tmp 4 | 5 | # Packages 6 | *.egg 7 | *.egg-info 8 | dist 9 | build 10 | eggs 11 | parts 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | #Translations 25 | *.mo 26 | 27 | #Mr Developer 28 | .mr.developer.cfg 29 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/helper.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | 5 | def getCkstylePath(): 6 | cmdPath = 'ckstyle' 7 | if sys.platform == 'linux2' or sys.platform == 'darwin': 8 | returnValue = os.popen3('which ckstyle') 9 | returnValue = returnValue[1].read() + returnValue[2].read() 10 | cmdPath = returnValue 11 | return cmdPath -------------------------------------------------------------------------------- /tests/unit/parser/ParserIgnoreSpeicalStatement.css: -------------------------------------------------------------------------------- 1 | @unit-test-expecteds { 2 | 0: should add @author in the head of "ParserIgnoreSpeicalStatement.css" 3 | } 4 | 5 | /* should not affect the parser */ 6 | @import url(lfjdalkfda); 7 | @charset fjdaslkfjd; 8 | @-css-compiler { 9 | fdas: fdas; 10 | } 11 | @-css-compiler-fdas fdkal; 12 | /* test */ 13 | .fda { 14 | width: 100px; 15 | } 16 | -------------------------------------------------------------------------------- /ckstyle/plugins/validators/MarginValidator.py: -------------------------------------------------------------------------------- 1 | class MarginValidator(): 2 | def __init__(self, name, value): 3 | self.name = name 4 | self.value = value 5 | 6 | def validate(self): 7 | realValues = [x for x in self.value.split(' ') if x != ''] 8 | if len(realValues) > 4: 9 | return False, 'value of margin is too much(items > 4)' 10 | return True, '' 11 | -------------------------------------------------------------------------------- /ckstyle/reporter/ReporterUtil.py: -------------------------------------------------------------------------------- 1 | from .TextReporter import TextReporter 2 | from .JsonReporter import JsonReporter 3 | from .XMLReporter import XMLReporter 4 | 5 | class ReporterUtil(): 6 | @staticmethod 7 | def getReporter(reporterType, checker): 8 | if reporterType == 'text': 9 | return TextReporter(checker) 10 | elif reporterType == 'json': 11 | return JsonReporter(checker) 12 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoAlphaImageLoader.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: should not use AlphaImageLoader in ".test" 6 | 1: value of "background-image" should use lower case, in ".test" 7 | } 8 | 9 | .test { 10 | background-image: filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://a.xnimg.cn/img/pop_dialog_top_left.png',sizingMethod='crop'); 11 | } 12 | -------------------------------------------------------------------------------- /tests/unit/check/FEDSelectorNoUnderLine.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: should not use _ in selector ".selector-with-_" 6 | 7 | 1: ".selector-with-no-underscore, .selector-with-_" contains the same rules in "FEDSelectorNoUnderLine.css" 8 | } 9 | 10 | 11 | .selector-with-no-underscore { 12 | width: 100px; 13 | } 14 | 15 | .selector-with-_ { 16 | width: 100px; 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/fix/FixFEDCss3PropSpaces.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | fixer, msg = doFix('.test {-webkit-border-radius: 3px;-moz-border-radius:3px;border-radius:3px;}', '') 5 | equal(msg, '.test {\n -webkit-border-radius: 3px;\n -moz-border-radius: 3px;\n border-radius: 3px;\n}', 'ok') 6 | 7 | fixer, msg = doFix('.test {border-radius:3px;}', '') 8 | equal(msg, '.test {\n border-radius: 3px;\n}', 'ok') 9 | -------------------------------------------------------------------------------- /tests/unit/entity/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | 9 | 10 | from ckstyle.entity.Rule import Rule 11 | from ckstyle.entity.RuleSet import RuleSet 12 | from ckstyle.entity.StyleSheet import StyleSheet 13 | from ckstyle.entity.ExtraStatement import ExtraStatement 14 | -------------------------------------------------------------------------------- /tests/unit/combiner/_just_margin.css: -------------------------------------------------------------------------------- 1 | .test { 2 | margin-left: 10px; 3 | margin-bottom: 10px; 4 | margin-right: 10px; 5 | margin-top: 20px; 6 | } 7 | 8 | .test2 { 9 | margin: 0 1px 2px 3px; 10 | margin-left: 10px; 11 | margin-bottom: 10px; 12 | margin-right: 10px; 13 | margin-top: 20px; 14 | } 15 | 16 | .test3 { 17 | margin: 20px 10px 10px; 18 | } 19 | .test4 { 20 | margin: 10px; 21 | margin-top: 20px; 22 | } 23 | -------------------------------------------------------------------------------- /tests/unit/combiner/_just_padding.css: -------------------------------------------------------------------------------- 1 | .test { 2 | padding-left: 10px; 3 | padding-bottom: 10px; 4 | padding-right: 10px; 5 | padding-top: 20px; 6 | } 7 | 8 | .test2 { 9 | padding: 0 1px 2px 3px; 10 | padding-left: 10px; 11 | padding-bottom: 10px; 12 | padding-right: 10px; 13 | padding-top: 20px; 14 | } 15 | 16 | .test3 { 17 | padding: 20px 10px 10px; 18 | } 19 | .test4 { 20 | padding: 10px; 21 | padding-top: 20px; 22 | } 23 | -------------------------------------------------------------------------------- /tests/unit/config/ckstyle_configed.ini: -------------------------------------------------------------------------------- 1 | [ckstyle] 2 | error-level = 2 3 | include = abc 4 | exclude = ddd 5 | recursive = true 6 | print-flag = true 7 | extension = .ckstyle2.txt 8 | tab-spaces = 2 9 | standard = standard2.css 10 | ignore-rule-sets = @unit-test-expecteds,@unit-tests-fda 11 | fix-to-single-line = true 12 | safe-mode = true 13 | no-bak = true 14 | 15 | [compress] 16 | extension = .min3.css 17 | combine-file = true 18 | browsers = ie6,ie7,std 19 | no-bak = true 20 | -------------------------------------------------------------------------------- /tests/unit/check/FEDUseSingleQuotation.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: replace " with ' in ".with-double-quotation" 6 | 0: can not set font-family for ".with-double-quotation" 7 | 0: can not set font-family for ".with-single-quotation" 8 | } 9 | 10 | 11 | .with-double-quotation { 12 | font-family: "Microsoft Yahei"; 13 | } 14 | 15 | .with-single-quotation { 16 | font-family: 'microsoft yahei'; 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoZeroBeforeDot.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: zero should be removed when meet 0.xxx in ".test" 6 | 1: zero should be removed when meet 0.xxx in ".test-another" 7 | 1: zero should be removed when meet 0.xxx in ".test-padding" 8 | } 9 | 10 | .test { 11 | width: 0.1px; 12 | } 13 | 14 | .test-another { 15 | width: 0.001px; 16 | } 17 | 18 | .test-padding { 19 | padding: 1px 0.2px; 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/check/FEDReplaceBorderZeroWithBorderNone.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: replace "border: 0" with "border: none" in ".test-border-zero" 6 | 1: replace "border-width: 0" with "border-width: none" in ".test-border-width-zero" 7 | } 8 | 9 | .test-border-none { 10 | border: none; 11 | } 12 | 13 | .test-border-zero { 14 | border: 0; 15 | } 16 | 17 | .test-border-width-zero { 18 | border-width: 0; 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/check/FEDMustContainAuthorInfoAnotherOK.css: -------------------------------------------------------------------------------- 1 | /** zhifu.wang@renren-inc.com 2 | */ 3 | @unit-test-expecteds { 4 | 1: ".test, .test-b, .test-c, .test-d" contains the same rules in "FEDMustContainAuthorInfoAnotherOK.css" 5 | } 6 | 7 | .test { 8 | width: 100px; 9 | } 10 | 11 | /* comment 1*/ 12 | /* comment 2*/ 13 | .test-b { 14 | width: 100px; 15 | } 16 | 17 | /* fa */ /* fda */ 18 | .test-c { 19 | width: 100px; 20 | } 21 | 22 | .test-d { 23 | width: 100px; 24 | } 25 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoEmptyRuleSet.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: find css comment (/* */) in ".testb" 6 | 0: empty ruleset found ".test" 7 | 0: empty ruleset found ".testa" 8 | 0: empty ruleset found ".testb" 9 | 1: ".test, .testa, .testb" contains the same rules in "FEDNoEmptyRuleSet.css" 10 | } 11 | 12 | .test { 13 | } 14 | 15 | .testa { 16 | 17 | 18 | } 19 | 20 | .testb { 21 | 22 | /***/ 23 | } 24 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoStarInSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: please remove low performance selector "*" from "* > div" 6 | 0: please remove low performance selector "*" from "div *" 7 | 0: do not use low performance selector ">" in "* > div" 8 | 1: "* > div, div *" contains the same rules in "FEDNoStarInSelector.css" 9 | } 10 | 11 | * > div { 12 | width: 100px; 13 | } 14 | 15 | div * { 16 | width: 100px; 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/check/FEDSemicolonAfterValue.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: each rule in ".abcd" need semicolon in the end, "border" has not 6 | 1: each rule in ".abcd" need semicolon in the end, "width" has not 7 | 1: each rule in ".abcd:hover" need semicolon in the end, "height" has not 8 | } 9 | 10 | .abcd { 11 | width: 0 12 | border: 1px solid red 13 | } 14 | 15 | .abcd:hover { 16 | width: 0; 17 | height: 200px 18 | } 19 | -------------------------------------------------------------------------------- /ckstyle.ini: -------------------------------------------------------------------------------- 1 | [ckstyle] 2 | error-level = 2 3 | include = all 4 | exclude = none 5 | recursive = false 6 | print-flag = false 7 | extension = .ckstyle.txt 8 | standard = standard.css 9 | ignore-rule-sets = @unit-test-expecteds 10 | fixed-extension = .fixed.css 11 | 12 | [compress] 13 | extension = .min.css 14 | combine-file = false 15 | browsers = false 16 | 17 | # TODOs 18 | [css-format] 19 | tab-spaces = 4 20 | 21 | [global-selectors] 22 | .nav, sidebar2 = home-frame2.css 23 | 24 | [plugin] 25 | demo = 111 26 | -------------------------------------------------------------------------------- /tests/unit/config/ckstyle.ini: -------------------------------------------------------------------------------- 1 | [ckstyle] 2 | error-level = 0 3 | include = all 4 | exclude = none 5 | recursive = false 6 | print-flag = false 7 | extension = .ckstyle.txt 8 | fixed-extension = .fixed.css 9 | standard = standard.css 10 | ignore-rule-sets = @unit-test-expecteds 11 | fix-to-single-line = false 12 | safe-mode = false 13 | no-bak = false 14 | 15 | [compress] 16 | extension = .min.css 17 | combine-file = false 18 | browsers = none 19 | no-bak = false 20 | 21 | [global-files] 22 | .nav, sidebar2 = home-frame2.css 23 | -------------------------------------------------------------------------------- /tests/unit/check/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.doCssCheck import doCheck 9 | 10 | def doCssCheck(fileContent, level = 2): 11 | checker = doCheck(fileContent) 12 | return checker.errors() 13 | 14 | def doCssTextCheck(text, fileName = ''): 15 | checker = doCheck(text, fileName) 16 | return checker.errors() 17 | -------------------------------------------------------------------------------- /tests/unit/check/FEDUnknownCssNameChecker.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: unknown attribute name "a" found in ".has-a" 6 | } 7 | 8 | .has-a { 9 | a: 1px solid red; 10 | } 11 | 12 | .behavior { 13 | behavior: test; 14 | } 15 | 16 | .khtml { 17 | -webkit-border-radius: 3px; 18 | -khtml-border-radius: 3px; 19 | -moz-border-radius: 3px; 20 | -ms-border-radius: 3px; 21 | -o-border-radius: 3px; 22 | border-radius: 3px; 23 | } 24 | -------------------------------------------------------------------------------- /tests/unit/combiner/_test.css: -------------------------------------------------------------------------------- 1 | /* @author wangjeaf */ 2 | 3 | @import (url-here); 4 | 5 | .test { 6 | _width: 100px; 7 | *height: 100px; 8 | } 9 | 10 | .test2 { 11 | _width: 100px; 12 | *height: 100px; 13 | } 14 | .test3 { 15 | _width: 100px; 16 | *height: 100px; 17 | } 18 | 19 | .test4 { 20 | _width: 100px; 21 | *height: 100px; 22 | } 23 | 24 | .test5 { 25 | *height: 100px; 26 | _width: 100px; 27 | } 28 | 29 | .test6 { 30 | _width: 100px; 31 | *height: 100px; 32 | display:none; 33 | } 34 | -------------------------------------------------------------------------------- /tests/unit/fix/FixFEDNoEmptyRuleSet.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | fixer, msg = doFix('.test {} .test2 {} .test3 {width:100px;} .test4 {}', '') 5 | 6 | styleSheet = fixer.getStyleSheet() 7 | equal(len(styleSheet.getRuleSets()), 1, 'one ruleset') 8 | ruleSet = styleSheet.getRuleSets()[0] 9 | equal(ruleSet.selector, '.test3', 'it is test3') 10 | width = ruleSet.getRuleByName('width') 11 | equal(width.fixedValue, "100px", 'width is fixed') 12 | equal(width.value, '100px', 'value of width is origin') 13 | -------------------------------------------------------------------------------- /tests/unit/check/demo.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | from ckstyle.reporter.helper import fill 3 | 4 | def doTest(): 5 | text = 'body {width: 1px}' 6 | logs, warns, errors = doCssCheck(text) 7 | equal(len(logs), 2, 'two logs') 8 | equal(len(warns), 1, 'one warn happened') 9 | equal(len(errors), 1, 'one error happened') 10 | equal(fill(warns[0]), r'each rule in "body" need semicolon in the end, "width" has not', 'warn rule text is ok') 11 | equal(fill(errors[0]), r'should not set style for html tag in "body"', 'error rule text is ok') 12 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Fixstyle", 4 | "command": "fixstyle" 5 | }, 6 | { 7 | "caption": "FixstyleSafe", 8 | "command": "fixstylesafe" 9 | }, 10 | { 11 | "caption": "FixstyleSingleLine", 12 | "command": "fixstylesingleline" 13 | }, 14 | { 15 | "caption": "CkStyle", 16 | "command": "ckstyle" 17 | }, 18 | { 19 | "caption": "CssCompress", 20 | "command": "csscompress" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /tests/unit/check/FEDMustContainAuthorInfo.css: -------------------------------------------------------------------------------- 1 | @unit-test-expecteds { 2 | 0: should add @author in the head of "FEDMustContainAuthorInfo.css" 3 | 1: ".test, .test-b, .test-c, .test-d" contains the same rules in "FEDMustContainAuthorInfo.css" 4 | } 5 | 6 | .test { 7 | width: 100px; 8 | } 9 | 10 | /** * @author : zhifu.wang@renren-inc.com 11 | */ 12 | /* comment 1*/ 13 | /* comment 2*/ 14 | .test-b { 15 | width: 100px; 16 | } 17 | 18 | /* fa */ /* fda */ 19 | .test-c { 20 | width: 100px; 21 | } 22 | 23 | .test-d { 24 | width: 100px; 25 | } 26 | -------------------------------------------------------------------------------- /tests/unit/fix/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.doCssFix import doFix 9 | import ckstyle.command.args as args 10 | defaultConfig = args.CommandArgs() 11 | 12 | def getFixed(css, name): 13 | fixer, msg = doFix(css, '') 14 | 15 | ruleSet = fixer.getStyleSheet().getRuleSets()[0] 16 | rule = ruleSet.getRuleByName(name) 17 | return rule.fixedValue 18 | -------------------------------------------------------------------------------- /tests/unit/check/FEDMustContainAuthorInfoAnother.css: -------------------------------------------------------------------------------- 1 | /** zhifu.wang@renren-inc.co 2 | */ 3 | @unit-test-expecteds { 4 | 0: should add @author in the head of "FEDMustContainAuthorInfoAnother.css" 5 | 1: ".test, .test-b, .test-c, .test-d" contains the same rules in "FEDMustContainAuthorInfoAnother.css" 6 | } 7 | 8 | .test { 9 | width: 100px; 10 | } 11 | 12 | /* comment 1*/ 13 | /* comment 2*/ 14 | .test-b { 15 | width: 100px; 16 | } 17 | 18 | /* fa */ /* fda */ 19 | .test-c { 20 | width: 100px; 21 | } 22 | 23 | .test-d { 24 | width: 100px; 25 | } 26 | -------------------------------------------------------------------------------- /ckstyle/browsers/Detector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | from .BinaryRule import * 4 | from .Hacks import doRuleDetect, doRuleSetDetect, doExtraDetect 5 | 6 | class Browser(): 7 | 8 | @staticmethod 9 | def handleRule(rule): 10 | rule.browser = doRuleDetect(rule.fixedName, rule.fixedValue) 11 | 12 | @staticmethod 13 | def handleRuleSet(ruleSet): 14 | ruleSet.browser = doRuleSetDetect(ruleSet.selector) 15 | 16 | @staticmethod 17 | def handleNestedStatement(statement): 18 | statement.browser = doExtraDetect(statement.selector) -------------------------------------------------------------------------------- /tests/unit/check/FEDShouldNotUseImportant.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: Should not use !important in "width" of ".test" 6 | 0: Should not use !important in "width" of ".testa" 7 | 0: Should not use !important in "width" of ".testb" 8 | 9 | 1: ".test, .testa" contains the same rules in "FEDShouldNotUseImportant.css" 10 | } 11 | 12 | .test { 13 | width: 100px !important; 14 | } 15 | 16 | .testa { 17 | width: 100px !important; 18 | } 19 | 20 | .testb { 21 | width: 100px ! important; 22 | } 23 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoAppearanceNameInSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: should not use appearance word "red" in ".color-red" 6 | 1: should not use appearance word "float" in ".float-left" 7 | 1: ".float-left, .topic" contains the same rules in "FEDNoAppearanceNameInSelector.css" 8 | } 9 | 10 | .is-correct-name { 11 | width: 100px; 12 | height: 200px; 13 | } 14 | 15 | .color-red { 16 | color: red; 17 | } 18 | 19 | .float-left { 20 | float: left; 21 | } 22 | 23 | .topic { 24 | float: left; 25 | } 26 | -------------------------------------------------------------------------------- /utils/combineRules.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | import os 4 | import json 5 | 6 | def loadPlugins(pluginDir): 7 | ids = [] 8 | content = '' 9 | '''从plugins目录动态载入检查类''' 10 | for filename in os.listdir(pluginDir): 11 | if not filename.endswith('.py') or filename.startswith('_'): 12 | continue 13 | if filename == 'Base.py' or filename == 'helper.py': 14 | continue 15 | content = content + open(pluginDir + '/' + filename, 'r').read() 16 | 17 | open('AllRules.py', 'w').write(content); 18 | 19 | loadPlugins('../ckstyle/plugins') -------------------------------------------------------------------------------- /tests/unit/check/FEDNoEmptyFile.py: -------------------------------------------------------------------------------- 1 | from asserts import ok, equal, notEqual, getResults 2 | from helper import doCssCheck, doCssTextCheck 3 | from ckstyle.reporter.helper import fill 4 | 5 | def doTest(): 6 | logs, warns, errors = doCssTextCheck('/* @author: zhifu.wang**/ /* .test {width: 100px;}*/', 'test.css') 7 | equal(len(errors), 1, 'one error occur') 8 | equal(fill(errors[0]), 'empty css file "test.css"') 9 | 10 | logs, warns, errors = doCssTextCheck('/* @author: zhifu.wang**/ /* .test {width: 100px;}*/ \n.test { width: 100px; }', 'test.css') 11 | equal(len(errors), 0, 'no error now') 12 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDUnknownCssNameChecker.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isCssProp 6 | 7 | class FEDUnknownCssNameChecker(RuleChecker): 8 | 9 | '''{ 10 | "summary":"错误的css属性", 11 | "desc":"本工具会帮您查找错误的CSS属性,如果写错了,即可收到错误提示" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'unknown-css-prop' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg = 'unknown attribute name "${name}" found in "${selector}"' 18 | 19 | def check(self, rule, config): 20 | return isCssProp(rule.name.lower()) 21 | -------------------------------------------------------------------------------- /tests/unit/asserts.py: -------------------------------------------------------------------------------- 1 | results = [] 2 | 3 | def ok(expected, msg = 'ok'): 4 | results.append([expected, msg]) 5 | 6 | def equal(expected, actual, msg = 'ok'): 7 | if expected == actual: 8 | ok(True, msg) 9 | else: 10 | ok(False, msg + ' (expect: %s, actual: %s)' % (expected, actual)) 11 | 12 | def notEqual(expected, actual, msg = 'ok'): 13 | if expected != actual: 14 | ok(True, msg) 15 | else: 16 | ok(False, msg + ' (%s is equal to %s)' % (expected, actual)) 17 | 18 | def getResults(): 19 | global results 20 | final = results 21 | results = [] 22 | return final 23 | -------------------------------------------------------------------------------- /tests/unit/check/FEDTransChnFontFamilyNameIntoEng.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: can not set font-family for ".test-chn" 6 | 0: can not set font-family for ".test-chn-font" 7 | 0: can not set font-family for ".test-eng" 8 | 0: should not use chinese font family name in ".test-chn" 9 | 0: should not use chinese font family name in ".test-chn-font" 10 | } 11 | 12 | .test-chn { 13 | font-family: '宋体'; 14 | } 15 | 16 | .test-chn-font { 17 | font: italic bold 12px/30px '宋体', '黑体'; 18 | } 19 | 20 | .test-eng { 21 | font-family: sarif; 22 | } 23 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDDistinguishBrowserRule.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | import string 6 | from ckstyle.browsers.Detector import Browser 7 | 8 | class FEDDistinguishBrowserRule(RuleChecker): 9 | 10 | '''{ 11 | "summary":"在属性级别区分浏览器", 12 | "desc":"目的是针对不同的浏览器,生成不同的CSS" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'rule-for-browsers' 17 | self.errorLevel = ERROR_LEVEL.ERROR 18 | self.errorMsg = '' 19 | 20 | def check(self, rule, config): 21 | return True 22 | 23 | def fix(self, rule, config): 24 | Browser.handleRule(rule) -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/README.md: -------------------------------------------------------------------------------- 1 | ##ckstyle plugin for Sublime Text 2 2 | 3 | ### 安装说明 4 | * 利用Sublime Text菜单 `references --> Browse Packages...` 找到Sublime Text 2的插件目录,例如:`C:\Documents and Settings\Administrator\Application Data\Sublime Text 2\Packages` 5 | * 将 `fixstyle_for_sublime_text_2.zip` 内的内容解压缩到插件目录下即可 6 | 7 | ### 命令说明 8 | 9 | * `Fixstyle` 为样式修复 10 | * `FixstyleSingleLine` 修复成单行模式 11 | * `FixstyleSafe` 安全模式修复 12 | * `CkStyle` 检查代码问题 13 | * `CssCompress` 为代码压缩 14 | 15 | ### 使用方法 16 | 17 | * Fixstyle快捷键:`Ctrl + F1` ,其他功能无快捷键 18 | 19 | * 菜单项:`tools->Fixstyle` `tools->CssCompress` 等 20 | 21 | * 右键菜单 `Fixstyle` `CssCompress` 等 22 | -------------------------------------------------------------------------------- /tests/unit/check/FED16ColorShouldUpper.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: replace "#DDDDDD" with "#DDD" in ".test" 6 | 1: replace "#AABBCC" with "#ABC" in ".test-c" 7 | 1: wrong color length(should be 3 or 6) in ".test-wrong-len" 8 | 1: color should in upper case in ".test-lower" 9 | } 10 | 11 | .test-lower { 12 | color: #fff; 13 | } 14 | 15 | .test { 16 | color: #DDDDDD; 17 | } 18 | 19 | .test-a { 20 | color: #DDD; 21 | } 22 | 23 | .test-b { 24 | color: #ABC; 25 | } 26 | 27 | .test-c { 28 | color: #AABBCC; 29 | } 30 | 31 | .test-wrong-len { 32 | color: #DDFDD; 33 | } 34 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDDistinguishBrowserRuleSet.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | import string 6 | from ckstyle.browsers.Detector import Browser 7 | 8 | class FEDDistinguishBrowserRuleSet(RuleSetChecker): 9 | 10 | '''{ 11 | "summary":"在规则集级别区分浏览器", 12 | "desc":"目的是针对不同的浏览器,生成不同的CSS规则集" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'ruleset-for-browsers' 17 | self.errorLevel = ERROR_LEVEL.ERROR 18 | self.errorMsg = '' 19 | 20 | def check(self, ruleSet, config): 21 | return True 22 | 23 | def fix(self, ruleSet, config): 24 | Browser.handleRuleSet(ruleSet) -------------------------------------------------------------------------------- /ckstyle/plugins/FEDSemicolonAfterValue.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDSemicolonAfterValue(RuleChecker): 7 | 8 | '''{ 9 | "summary":"为每一个属性后添加分号", 10 | "desc":"按照CSS编码规范,每一个规则后面都必须加上分号 ;" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'add-semicolon' 15 | self.errorLevel = ERROR_LEVEL.WARNING 16 | self.errorMsg = 'each rule in "${selector}" need semicolon in the end, "${name}" has not' 17 | 18 | def check(self, rule, config): 19 | if not rule.roughValue.strip().endswith(';'): 20 | return False 21 | return True 22 | -------------------------------------------------------------------------------- /tests/experiment/test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from ckstyle.CssCheckerWrapper import doCheck 3 | from ckstyle.reporter.ReporterUtil import ReporterUtil 4 | from ckstyle.cssparser.CssFileParser import CssParser 5 | from ckstyle.entity.StyleSheet import StyleSheet 6 | 7 | def checkCssFileByOpm(filePath): 8 | fileContent = open(filePath).read() 9 | checker = doCheck(fileContent, filePath) 10 | if checker.hasError(): 11 | reporter = ReporterUtil.getReporter('text', checker) 12 | reporter.doReport() 13 | print reporter.export() 14 | return False 15 | return True 16 | 17 | 18 | if __name__ == '__main__': 19 | checkCssFileByOpm('test.css') 20 | -------------------------------------------------------------------------------- /tests/unit/order/Try.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | fixer, msg = doFix('.test {width:100px; display:none;}', '') 5 | 6 | styleSheet = fixer.getStyleSheet() 7 | equal(len(styleSheet.getRuleSets()), 1, 'one ruleset') 8 | ruleSet = styleSheet.getRuleSets()[0] 9 | equal(ruleSet.selector, '.test', 'it is the selector that i need') 10 | 11 | rules = ruleSet.getRules() 12 | equal(rules[0].name, 'display', 'first element is display now') 13 | equal(rules[1].name, 'width', 'second element is width') 14 | 15 | equal(rules[0].value, 'none', 'first element value is ok') 16 | equal(rules[1].value, '100px', 'second element value is ok') 17 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDFixOutlineZero.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDFixOutlineZero(RuleChecker): 7 | 8 | '''{ 9 | "summary":"修复outline:none", 10 | "desc":"outline:noneoutline:0 实现了相同的功能,但是后者的代码更简洁,便于压缩。" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'outline-zero' 15 | self.errorLevel = ERROR_LEVEL.WARNING 16 | self.errorMsg = '' 17 | 18 | def check(self, rule, config): 19 | return True 20 | 21 | def fix(self, rule, config): 22 | if rule.name == 'outline' and rule.fixedValue == 'none': 23 | rule.fixedValue = '0' -------------------------------------------------------------------------------- /tests/unit/fix/FixPropStartWithSharp.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | go() 5 | 6 | def go(): 7 | css = '''.theme-hot li { 8 | float: left; 9 | padding: 0 10px 0 2px; 10 | #padding: 1px 10px 0 2px; 11 | padding: 1px 10px 0 2px\\0; 12 | _padding: 3px 10px 0 2px; 13 | }''' 14 | 15 | expectedFixed = '''.theme-hot li { 16 | float: left; 17 | padding: 0 10px 0 2px; 18 | #padding: 1px 10px 0 2px; 19 | padding: 1px 10px 0 2px\\0; 20 | _padding: 3px 10px 0 2px; 21 | }''' 22 | 23 | fixer, msg = doFix(css, '') 24 | equal(msg.strip(), expectedFixed.strip(), '#padding is ok, do not combine attr which in hack') 25 | 26 | -------------------------------------------------------------------------------- /tests/unit/check/FEDDoNotSetStyleForTagOnly.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: should not set style for html tag in "div" 6 | 0: should not set style for html tag in "div, .class" 7 | 0: should not set style for html tag in "div, html" 8 | 1: "div, div, .class, div, html, .test div, div:hover" contains the same rules in "FEDDoNotSetStyleForTagOnly.css" 9 | } 10 | 11 | div { 12 | width: 100px; 13 | } 14 | 15 | div, 16 | .class { 17 | width: 100px; 18 | } 19 | 20 | div, 21 | html { 22 | width: 100px; 23 | } 24 | 25 | .test div { 26 | width: 100px; 27 | } 28 | 29 | div:hover { 30 | width: 100px; 31 | } 32 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDShouldNotUseImportant.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDShouldNotUseImportant(RuleChecker): 7 | 8 | '''{ 9 | "summary":"不要使用!important", 10 | "desc":"CSS中不要使用!important" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'do-not-use-important' 15 | self.errorLevel = ERROR_LEVEL.ERROR 16 | self.errorMsg = 'Should not use !important in "${name}" of "${selector}"' 17 | 18 | def check(self, rule, config): 19 | value = rule.value 20 | if value.replace(' ', '').find('!important') != -1: 21 | return False 22 | return True 23 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDSelectorNoUnderLine.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDSelectorNoUnderLine(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"不要在选择器中使用下划线", 10 | "desc":"在selector中不要使用下划线 _ ,可以使用中划线 -" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'no-underline-in-selector' 15 | self.errorLevel = ERROR_LEVEL.WARNING 16 | self.errorMsg = 'should not use _ in selector "${selector}"' 17 | 18 | def check(self, ruleSet, config): 19 | selector = ruleSet.selector 20 | if selector.find('_') != -1: 21 | return False 22 | return True 23 | -------------------------------------------------------------------------------- /tests/unit/check/FEDUseLowerCaseSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: selector should use lower case, in ".IS-UPPER" 6 | 1: should not use _ in selector ".is_uppeR" 7 | 1: selector should use lower case, in ".is_uppeR" 8 | 1: selector should use lower case, in ".AAA, .BBB" 9 | 10 | 1: ".IS-UPPER, .is_uppeR, .AAA, .BBB" contains the same rules in "FEDUseLowerCaseSelector.css" 11 | } 12 | 13 | .is-correct-lower-selector { 14 | width: 100px; 15 | height: 200px; 16 | } 17 | 18 | .IS-UPPER { 19 | width: 100px; 20 | } 21 | 22 | .is_uppeR { 23 | width: 100px; 24 | } 25 | 26 | .AAA, 27 | .BBB { 28 | width: 100px; 29 | } 30 | -------------------------------------------------------------------------------- /tests/unit/fix/Try.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | fixer, msg = doFix('.test {width:"100px";color:#DDDDDD;}', '') 5 | 6 | styleSheet = fixer.getStyleSheet() 7 | equal(len(styleSheet.getRuleSets()), 1, 'one ruleset') 8 | ruleSet = styleSheet.getRuleSets()[0] 9 | equal(ruleSet.selector, '.test', 'it is the selector that i need') 10 | width = ruleSet.getRuleByName('width') 11 | equal(width.fixedValue, "'100px'", 'width is fixed') 12 | equal(width.value, '"100px"', 'value of width is origin') 13 | 14 | color = ruleSet.getRuleByName('color') 15 | equal(color.fixedValue, '#DDD', 'color is fixed') 16 | equal(color.value, '#DDDDDD', 'value of color is origin') 17 | -------------------------------------------------------------------------------- /tests/unit/check/FEDCanNotSetFontFamily.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: can not set font-family for ".set-font-family" 6 | 0: can not set font-family for ".set-by-font" 7 | 0: can not set font-family for ".set-by-font-but-no-comma" 8 | 0: can not set font-family for ".set-by-font-but-no-comma-another" 9 | } 10 | 11 | .set-font-family { 12 | font-family: 'arial,sans-serif'; 13 | } 14 | 15 | .set-by-font { 16 | font: italic bold 12px/30px Georgia, serif; 17 | } 18 | 19 | .set-by-font-but-no-comma { 20 | font: italic bold 12px/30px Georgia; 21 | } 22 | 23 | .set-by-font-but-no-comma-another { 24 | font: italic bold Georgia; 25 | } 26 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDDistinguishBrowserExtra.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | import string 6 | from ckstyle.browsers.Detector import Browser 7 | 8 | class FEDDistinguishBrowserExtra(ExtraChecker): 9 | 10 | '''{ 11 | "summary":"嵌套规则区分浏览器", 12 | "desc":"目的是针对不同的浏览器,生成不同的CSS规则集" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'extra-for-browsers' 17 | self.errorLevel = ERROR_LEVEL.ERROR 18 | self.errorMsg = '' 19 | 20 | def check(self, ruleSet, config): 21 | return True 22 | 23 | def fix(self, ruleSet, config): 24 | if not ruleSet.nested: 25 | return 26 | Browser.handleNestedStatement(ruleSet) -------------------------------------------------------------------------------- /tests/unit/compress/CompressCssCompiler.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _no_space() 5 | _has_space() 6 | _just_prefix() 7 | 8 | def _no_space(): 9 | msg = doCssCompress('@-css-compiler{selector-compile:no-combinator;rule-compile:all}html{width:100px;}') 10 | equal(msg, 'html{width:100px}', '@css-compiler compressed') 11 | 12 | def _has_space(): 13 | msg = doCssCompress('@-css-compiler {selector-compile:no-combinator;rule-compile:all}html{width:100px;}') 14 | equal(msg, 'html{width:100px}', '@css-compiler compressed') 15 | 16 | def _just_prefix(): 17 | msg = doCssCompress('@-css-compiler-prefix fdsafdsafdsa;html{width:100px;}') 18 | equal(msg, 'html{width:100px}', '@css-compiler compressed') -------------------------------------------------------------------------------- /tests/unit/combiner/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.doCssCompress import doCompress 9 | 10 | def doCssCompress(fileContent, fileName = ''): 11 | checker, compressed = doCompress(fileContent, fileName) 12 | return compressed 13 | 14 | def realpath(filepath): 15 | dirpath = os.path.realpath(os.path.join(__file__, '../')) 16 | path = os.path.join(dirpath, filepath) 17 | return path 18 | 19 | def doCssFileCompress(path): 20 | fileContent = open(realpath(path), 'r').read() 21 | return doCssCompress(fileContent, path) 22 | -------------------------------------------------------------------------------- /tests/unit/config/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | import ckstyle.command.CommandFileParser as CommandFileParser 9 | from ckstyle.command.ConsoleCommandParser import parseCkStyleCmdArgs, parseCompressCmdArgs, parseFixStyleCmdArgs 10 | 11 | def realpath(filepath): 12 | dirpath = os.path.realpath(os.path.join(__file__, '../')) 13 | path = os.path.join(dirpath, filepath) 14 | return path 15 | 16 | def parseConfigFile(path): 17 | parser = CommandFileParser.CommandFileParser(realpath(path), True) 18 | config = parser.args 19 | return config 20 | -------------------------------------------------------------------------------- /tests/unit/fix/FixRuleSetsFormat.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _singleLine() 5 | _multiLine() 6 | 7 | def _singleLine(): 8 | defaultConfig.fixToSingleLine = True 9 | fixer, msg = doFix('.test {width:"100px";color:#DDDDDD;} .test2 {width:"100px";color:#DDDDDD;}', fileName = '', config = defaultConfig) 10 | defaultConfig.fixToSingleLine = False 11 | equal(msg, '''.test, 12 | .test2 { width: '100px'; color: #DDD; }''', 'fix to single line is ok') 13 | 14 | def _multiLine(): 15 | fixer, msg = doFix('.test {width:"100px";color:#DDDDDD;} .test2 {width:"100px";color:#DDDDDD;}', '') 16 | equal(msg, '''.test, 17 | .test2 { 18 | width: '100px'; 19 | color: #DDD; 20 | }''', 'fix to multi line is ok') 21 | -------------------------------------------------------------------------------- /tests/unit/hacks/HackRuleSets.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | equal(doRuleSetDetect('* html .test'), IE6, '* html') 5 | equal(doRuleSetDetect('* + html .test'), IE7, '* + html') 6 | equal(doRuleSetDetect('*:first-child+html .test'), IE7, '*:first-child+html .test') 7 | equal(doRuleSetDetect('html > body .test'), IE7 | IE8 | IE9PLUS, 'html > body') 8 | equal(doRuleSetDetect('html>/**/body .test'), IE8 | IE9PLUS, 'html>/**/body') 9 | equal(doRuleSetDetect('::-webkit-selection {}'), WEBKIT, '::-webkit-selection') 10 | equal(doRuleSetDetect('::-moz-selection {}'), FIREFOX, '::-moz-selection') 11 | equal(doRuleSetDetect('::-ms-selection {}'), IE9PLUS, '::-ms-selection') 12 | equal(doRuleSetDetect('::-o-selection {}'), OPERA, '::-o-selection') -------------------------------------------------------------------------------- /tests/unit/config/WithPlugin.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _default() 5 | 6 | def _default(): 7 | config = parseCkStyleCmdArgs(realpath('ckstyle_with_plugin.ini'), [], [], True) 8 | ok(config.pluginConfig is not None, 'plugin config is not none') 9 | 10 | options = config.pluginConfig 11 | ok(options.has_key('plugin-a-config'), 'plugin config a') 12 | ok(options.has_key('plugin-b-config'), 'plugin config b') 13 | # config in lower case 14 | ok(options.has_key('pluginCConfig'), 'plugin c') 15 | 16 | equal(options.get('plugin-a-config'), '1', 'value of plugin config a') 17 | equal(options.get('plugin-b-config'), '2', 'value of plugin config b') 18 | equal(options.get('pluginCConfig'), '3', 'value of plugin config c') -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoEmptyRuleSet.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDNoEmptyRuleSet(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"删除空的规则", 10 | "desc":"空的CSS规则集是没有任何意义的,应该直接删除掉" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'no-empty-ruleset' 15 | self.errorLevel = ERROR_LEVEL.ERROR 16 | self.errorMsg = 'empty ruleset found "${selector}"' 17 | 18 | def check(self, ruleSet, config): 19 | if len(ruleSet.getRules()) == 0: 20 | return False 21 | return True 22 | 23 | def fix(self, ruleSet, config): 24 | if len(ruleSet.getRules()) == 0: 25 | styleSheet = ruleSet.getStyleSheet() 26 | styleSheet.removeRuleSet(ruleSet) 27 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoAlphaImageLoader.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDNoAlphaImageLoader(RuleChecker): 7 | 8 | '''{ 9 | "summary":"不要使用AlphaImageLoader", 10 | "desc":"AlphaImageLoader 主要用于在IE6下显示半透明图片,此举实际上费力不讨好, 11 | 对IE的性能影响极大,为了更好地实现网页的 渐进增强 12 | ,建议不要使用 AlphaImageLoader" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'no-alpha-image-loader' 17 | self.errorLevel = ERROR_LEVEL.WARNING 18 | self.errorMsg = 'should not use AlphaImageLoader in "${selector}"' 19 | 20 | def check(self, rule, config): 21 | if rule.value.find('AlphaImageLoader') != -1: 22 | return False 23 | return True 24 | -------------------------------------------------------------------------------- /tests/unit/check/FEDDoNotSetStyleForSimpleSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: should not set style for ".nav" in ".nav" 6 | 0: should not set style for "#sidebar" in "#sidebar" 7 | 0: should not set style for ".nav" in ".nav-b, .nav" 8 | 1: ".nav-b, .nav, .nav:hover" contains the same rules in "FEDDoNotSetStyleForSimpleSelector.css" 9 | 1: "#side-bar, #sidebar" contains the same rules in "FEDDoNotSetStyleForSimpleSelector.css" 10 | } 11 | 12 | .nav { 13 | width: 100px; 14 | height: 200px; 15 | } 16 | 17 | #side-bar { 18 | float: left; 19 | } 20 | 21 | #sidebar { 22 | float: left; 23 | } 24 | 25 | .nav-b, 26 | .nav { 27 | width: 100px; 28 | } 29 | 30 | .nav:hover { 31 | width: 100px; 32 | } 33 | -------------------------------------------------------------------------------- /ckstyle/cmdconsole/ConsoleClass.py: -------------------------------------------------------------------------------- 1 | DEBUG = False 2 | PREFIX = '[CKstyle %s] ' 3 | 4 | class console(): 5 | 6 | @staticmethod 7 | def show(msg, t=""): 8 | if t != "": 9 | print(PREFIX % t + msg) 10 | else: 11 | print(msg) 12 | 13 | @staticmethod 14 | def showError(msg): 15 | console.show(msg, "ERROR") 16 | 17 | @staticmethod 18 | def showOk(msg): 19 | console.show(msg, "OK") 20 | 21 | @staticmethod 22 | def log(msg): 23 | if DEBUG: 24 | consle.show(msg, "LOG") 25 | 26 | @staticmethod 27 | def warn(msg): 28 | if DEBUG: 29 | console.show(msg, "WARN") 30 | 31 | @staticmethod 32 | def error(msg): 33 | if DEBUG: 34 | console.show(msg, "ERROR") 35 | -------------------------------------------------------------------------------- /tests/unit/check/FEDHackAttributeInCorrectWay.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: unknown attribute name "^width" found in ".hack-by-extra-char" 6 | 0: "width" is not in correct hacking way in ".error-hack-ie" 7 | 1: ".no-hack, .hack-ie-six, .hack-ie-seven, .hack-ie-six-and-seven" contains the same rules in "FEDHackAttributeInCorrectWay.css" 8 | } 9 | 10 | .hack-by-extra-char { 11 | ^width: 100px; 12 | } 13 | 14 | .no-hack { 15 | width: 200px; 16 | } 17 | 18 | .hack-ie-six { 19 | _width: 200px; 20 | } 21 | 22 | .hack-ie-seven { 23 | +width: 200px; 24 | } 25 | 26 | .hack-ie-six-and-seven { 27 | *width: 200px; 28 | } 29 | 30 | .hack-ies { 31 | width: 200px\9; 32 | } 33 | 34 | .error-hack-ie { 35 | width: 200px\0; 36 | } 37 | -------------------------------------------------------------------------------- /tests/unit/check/FEDSingleLineSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: should not "enter" at the end of ".single-has-enter" 6 | 2: should start with "space" in ".single-extra-space" 7 | 2: should have "only one space" before the opening brace in ".many-spaces-end" 8 | 2: selector should end with only one space ".single-has-enter" 9 | 2: selector should end with only one space ".many-spaces-end" 10 | 11 | 1: ".single-has-enter, .single-extra-space, .many-spaces-end" contains the same rules in "FEDSingleLineSelector.css" 12 | } 13 | 14 | .single-ok { width: 100px; height: 100px; } 15 | 16 | .single-has-enter 17 | { 18 | width: 100px; 19 | } 20 | 21 | .single-extra-space { width: 100px; } 22 | 23 | .many-spaces-end { width: 100px; } 24 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDCommentLengthLessThan80.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDCommentLengthLessThan80(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"注释不能超过80个字符", 10 | "desc":"注释长度不能超过80个字符,40个汉字,如果超出,则应该要换行~" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'comment-length' 15 | self.errorLevel = ERROR_LEVEL.LOG 16 | self.errorMsg = 'comment for "${selector}" length should less than 80 per line' 17 | 18 | def check(self, ruleSet, config): 19 | comment = ruleSet.roughComment 20 | if len(comment) == 0: 21 | return True 22 | 23 | cs = comment.split('\n') 24 | for c in cs: 25 | if len(c.strip()) > 80: 26 | return False 27 | return True 28 | -------------------------------------------------------------------------------- /tests/unit/combiner/_test_different_order.css: -------------------------------------------------------------------------------- 1 | .test1 { 2 | width: 100px; 3 | height: 200px; 4 | *display: none; 5 | border: 1px solid #FFFFFF; 6 | _display: inline-block; 7 | } 8 | 9 | .test2 { 10 | *display: none; 11 | width: 100px; 12 | border: 1px solid #FFF; 13 | height: 200px; 14 | _display: inline-block; 15 | } 16 | 17 | .test3 { 18 | border: 1px solid #fff; 19 | width: 100px; 20 | height: 200px; 21 | *display: none; 22 | _display: inline-block; 23 | } 24 | 25 | .test4 { 26 | border: 1px solid #ffffff; 27 | *display: none; 28 | width: 100px; 29 | height: 200px; 30 | _display: inline-block; 31 | } 32 | 33 | .test5 { 34 | width: 100px; *display: none; height: 200px; 35 | border: 1px solid #ffffff; 36 | _display: inline-block; 37 | } 38 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/CkStylePlugin.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import sublime, sublime_plugin 3 | import os 4 | from helper import getCkstylePath 5 | 6 | class CkstyleCommand(sublime_plugin.TextCommand): 7 | 8 | def run(self, edit): 9 | path = os.path.realpath(self.view.file_name().decode('utf-8')) 10 | if os.path.splitext(path)[1] != '.css': 11 | sublime.error_message('Not a CSS file!') 12 | return 13 | 14 | cmd = getCkstylePath() + ' check "' + path + '"' 15 | os.popen3(cmd) 16 | resultFile = self.view.file_name() + '.ckstyle.txt' 17 | if os.path.exists(resultFile): 18 | self.view.window().open_file(self.view.file_name() + '.ckstyle.txt') 19 | else: 20 | sublime.message_dialog('No mistake found in this CSS, NB!') 21 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDUseSingleQuotation.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDUseSingleQuotation(RuleChecker): 7 | 8 | '''{ 9 | "summary":"使用单引号", 10 | "desc":"CSS的属性取值一律使用单引号', 不允许使用双引号" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'single-quotation' 15 | self.errorLevel = ERROR_LEVEL.WARNING 16 | self.errorMsg = 'replace " with \' in "${selector}"' 17 | 18 | def check(self, rule, config): 19 | if self._findDouble(rule.value): 20 | return False 21 | 22 | return True 23 | 24 | def fix(self, rule, config): 25 | if self._findDouble(rule.value): 26 | rule.fixedValue = rule.value.replace('"', "'") 27 | 28 | def _findDouble(self, value): 29 | return value.find('"') != -1 30 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDUseValidValues.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .validators.ValidatorFactory import doValidate 6 | 7 | class FEDUseValidValues(RuleChecker): 8 | 9 | '''{ 10 | "summary":"不正确的属性取值", 11 | "desc":"检查不正确的属性取值,比如: width: underline; 等" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'valid-values' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg_rough = '%s in "${selector}"' 18 | self.errorMsg = '' 19 | 20 | def check(self, rule, config): 21 | flag, msg = doValidate(rule.name, rule.strippedValue) 22 | if flag is True: 23 | return True 24 | 25 | self.errorMsg = self.errorMsg_rough % msg 26 | return False 27 | 28 | def fix(self, rule, config): 29 | pass 30 | -------------------------------------------------------------------------------- /tests/unit/check/FEDZIndexShouldInRange.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: value of "z-index" is not correct in ".z-index-minus" 6 | 0: value of "z-index" is not correct in ".z-index-exceed" 7 | 0: value of "z-index" is not correct in ".z-index-exceed-more" 8 | } 9 | 10 | .z-index-minus { 11 | z-index: -1000; 12 | } 13 | 14 | .z-index-minus-one { 15 | z-index: -1; 16 | } 17 | 18 | .z-index-zero { 19 | z-index: 0; 20 | } 21 | 22 | .z-index-one { 23 | z-index: 1; 24 | } 25 | 26 | .z-index-little-less { 27 | z-index: 1999; 28 | } 29 | 30 | .z-index-little-less-beta { 31 | z-index: 2099; 32 | } 33 | 34 | .z-index-max { 35 | z-index: 2100; 36 | } 37 | 38 | .z-index-exceed { 39 | z-index: 2101; 40 | } 41 | 42 | .z-index-exceed-more { 43 | z-index: 100000; 44 | } 45 | -------------------------------------------------------------------------------- /tests/unit/combiner/_with_margin.css: -------------------------------------------------------------------------------- 1 | /* @author wangjeaf */ 2 | 3 | .test { 4 | _width: 100px; 5 | *height: 100px; 6 | margin: 10px; 7 | margin-top: 20px; 8 | } 9 | 10 | .test2 { 11 | _width: 100px; 12 | *height: 100px; 13 | margin: 20px 10px 10px; 14 | } 15 | .test3 { 16 | _width: 100px; 17 | *height: 100px; 18 | margin: 0 10px 0px; 19 | margin-top: 20px; 20 | margin-left: 10px; 21 | margin-right: 10px; 22 | margin-bottom: 10px; 23 | } 24 | 25 | .test4 { 26 | margin: 10px; 27 | _width: 100px; 28 | margin-top: 20px; 29 | *height: 100px; 30 | } 31 | 32 | .test5 { 33 | margin: 10px; 34 | margin-top: 20px; 35 | margin-left: 10px; 36 | *height: 100px; 37 | _width: 100px; 38 | } 39 | 40 | .test6 { 41 | _width: 100px; 42 | *height: 100px; 43 | display:none; 44 | } 45 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoCommentInValues.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDNoCommentInValues(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"不要在css属性中添加注释", 10 | "desc":"CSS的注释应该写在 selector 前面,属性中不允许添加css注释,例如:
11 | .selector {
12 |     width: 100px;/*comment here*/
13 | } 14 | " 15 | }''' 16 | 17 | def __init__(self): 18 | self.id = 'no-comment-in-value' 19 | self.errorLevel = ERROR_LEVEL.LOG 20 | self.errorMsg = 'find css comment (/* */) in "${selector}"' 21 | 22 | def check(self, ruleSet, config): 23 | if ruleSet.roughValue.find('/*') != -1 or ruleSet.roughValue.find('*/') != -1: 24 | return False 25 | return True 26 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDTransChnFontFamilyNameIntoEng.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import containsChnChar 6 | 7 | class FEDTransChnFontFamilyNameIntoEng(RuleChecker): 8 | 9 | '''{ 10 | "summary":"字体设置时使用英文", 11 | "desc":"有的字体设置可以通过中文和英文两者方式来声明,比如
12 | 微软雅黑Microsoft Yahei ,我们推荐用英文的方式来实现" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'no-chn-font-family' 17 | self.errorLevel = ERROR_LEVEL.ERROR 18 | self.errorMsg = 'should not use chinese font family name in "${selector}"' 19 | 20 | def check(self, rule, config): 21 | if rule.name != 'font' and rule.name != 'font-family': 22 | return True 23 | 24 | if containsChnChar(rule.value): 25 | return False 26 | 27 | return True 28 | -------------------------------------------------------------------------------- /tests/unit/combiner/_with_padding.css: -------------------------------------------------------------------------------- 1 | /* @author wangjeaf */ 2 | 3 | .test { 4 | _width: 100px; 5 | *height: 100px; 6 | padding: 10px; 7 | padding-top: 20px; 8 | } 9 | 10 | .test2 { 11 | _width: 100px; 12 | *height: 100px; 13 | padding: 20px 10px 10px; 14 | } 15 | .test3 { 16 | _width: 100px; 17 | *height: 100px; 18 | padding: 0 10px 0px; 19 | padding-top: 20px; 20 | padding-left: 10px; 21 | padding-right: 10px; 22 | padding-bottom: 10px; 23 | } 24 | 25 | .test4 { 26 | padding: 10px; 27 | _width: 100px; 28 | padding-top: 20px; 29 | *height: 100px; 30 | } 31 | 32 | .test5 { 33 | padding: 10px; 34 | padding-top: 20px; 35 | padding-left: 10px; 36 | *height: 100px; 37 | _width: 100px; 38 | } 39 | 40 | .test6 { 41 | _width: 100px; 42 | *height: 100px; 43 | display:none; 44 | } 45 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoUnitAfterZero.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: unit should be removed when meet 0 in ".test-zero-px" 6 | 1: zero should be removed when meet 0.xxx in ".test-zero-dot-one-px" 7 | 1: unit should be removed when meet 0 in ".test-padding" 8 | } 9 | 10 | .test-zero-px { 11 | width: 0px; 12 | } 13 | 14 | .test-zero { 15 | width: 0; 16 | } 17 | 18 | .test-zero-dot-one-px { 19 | width: 0.1px; 20 | } 21 | 22 | .test-padding { 23 | padding: 1px 0px; 24 | } 25 | 26 | .test-second { 27 | -webkit-animation: rotation 0s linear infinite; 28 | -moz-animation: rotation 0s linear infinite; 29 | -o-animation: rotation 0s linear infinite; 30 | animation: rotation 0s linear infinite; 31 | } 32 | 33 | .test-padding-start-with-zero { 34 | padding: 0 1px 0 1px; 35 | } 36 | -------------------------------------------------------------------------------- /tests/unit/entity/StyleSheet.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _stylesheet() 5 | 6 | def _stylesheet(): 7 | styleSheet = StyleSheet('test.css') 8 | equal(styleSheet.getFile(), 'test.css', 'file name is ok') 9 | styleSheet.setFile('test2.css') 10 | equal(styleSheet.getFile(), 'test2.css', 'file nam changed') 11 | equal(styleSheet._file, styleSheet.getFile(), 'it is the same') 12 | 13 | equal(len(styleSheet.getRuleSets()), 0, 'no rule sets') 14 | equal(styleSheet.getRuleSetBySelector('.test'), None, 'can not find .test') 15 | 16 | styleSheet.addRuleSetByStr('.test', 'width:100px;height:100px', '/* fjdalkf */') 17 | equal(len(styleSheet.getRuleSets()), 1, 'one rule set') 18 | equal(styleSheet.getRuleSetBySelector('.test').selector, '.test', 'find .test') 19 | equal(styleSheet._ruleSets[0].comment, '/* fjdalkf */', 'comment is ok') 20 | -------------------------------------------------------------------------------- /tests/unit/fix/FixFEDCombineInToOne.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | equal(getFixed('.test {margin:0 0 0 0}', 'margin'), '0', 'margin is fixed') 5 | equal(getFixed('.test {margin:0 auto 0 auto}', 'margin'), '0 auto', 'margin 2 is fixed') 6 | equal(getFixed('.test {margin:auto 0 0 auto}', 'margin'), 'auto 0 0 auto', 'margin 3 is fixed') 7 | equal(getFixed('.test {margin:0 0}', 'margin'), '0', 'margin 4 is fixed') 8 | equal(getFixed('.test {margin:0px 0}', 'margin'), '0', 'margin 5 is fixed') 9 | equal(getFixed('.test {margin:0px 1px 0px 1px}', 'margin'), '0 1px', 'margin 6 is fixed') 10 | equal(getFixed('.test {margin:0px auto 0px auto}', 'margin'), '0 auto', 'margin 7 is fixed') 11 | equal(getFixed('.test {margin:50px auto 0 auto}', 'margin'), '50px auto 0', 'margin 8 is fixed') 12 | equal(getFixed('.test {margin: -15px -15px 0 -15px}', 'margin'), '-15px -15px 0', 'margin 9 is fixed') 13 | -------------------------------------------------------------------------------- /tests/unit/entity/Rule.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _rule() 5 | _getRuleSet() 6 | 7 | def _getRuleSet(): 8 | ruleSet = RuleSet('.selector', 'width:100px;', '/* aa */', None) 9 | rule = Rule("", "", "", ruleSet) 10 | equal(rule.getRuleSet(), ruleSet, 'get rule set') 11 | equal(rule.getRuleSet().selector, '.selector', 'it is what I need') 12 | 13 | def _rule(): 14 | rule = Rule(" .test ", " _width ", " 100px; ", None) 15 | equal(rule.selector, '.test', 'selector is ok') 16 | equal(rule.roughSelector, ' .test ', 'roughSelector is ok') 17 | equal(rule.roughName, ' _width ', 'roughName is ok') 18 | equal(rule.name, 'width', 'name is ok') 19 | equal(rule.roughValue, ' 100px; ', 'roughValue is ok') 20 | equal(rule.value, '100px', 'value is ok') 21 | equal(rule.strippedName, '_width', 'stripped name is ok') 22 | equal(rule.strippedValue, '100px;', 'strippedValue is ok') 23 | -------------------------------------------------------------------------------- /tests/unit/hacks/HackExtras.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _standard_hack() 5 | _keyframes() 6 | 7 | def _standard_hack(): 8 | equal(doExtraDetect('@media screen and (max-device-width: 480px)'.replace(' ', '')), WEBKIT, 'webkit mobile hack is ok') 9 | equal(doExtraDetect('@media screen and (-webkit-min-device-pixel-ratio:0)'.replace(' ', '')), WEBKIT, 'webkit hack is ok') 10 | equal(doExtraDetect('@media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0)'.replace(' ', '')), OPERA, 'opera hack is ok') 11 | 12 | def _keyframes(): 13 | equal(doExtraDetect('@keyframes fda'), NONEIE | IE9PLUS, '@keyframes') 14 | equal(doExtraDetect('@-webkit-keyframes fda'), WEBKIT, '@-webkit-keyframes') 15 | equal(doExtraDetect('@-moz-keyframes fda'), FIREFOX, '@-moz-keyframes') 16 | equal(doExtraDetect('@-ms-keyframes fda'), IE9PLUS, '@-ms-keyframes') 17 | equal(doExtraDetect('@-o-keyframes fda'), OPERA, '@-o-keyframes') -------------------------------------------------------------------------------- /ckstyle/plugins/FEDDoNotSetStyleForTagOnly.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isHTMLTag 6 | 7 | class FEDDoNotSetStyleForTagOnly(RuleSetChecker): 8 | 9 | '''{ 10 | "summary":"不要为html tag设置样式", 11 | "desc":"除了重置 CSS(如Reset.css) 的相关设置,其他代码一律不允许为html tag设置样式。" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'no-style-for-tag' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg = 'should not set style for html tag in "${selector}"' 18 | 19 | def check(self, ruleSet, config): 20 | selector = ruleSet.selector.lower() 21 | if selector.find('@media') != -1: 22 | return True 23 | if selector.find('@-moz-document') != -1: 24 | return True 25 | selectors = selector.split(',') 26 | for s in selectors: 27 | if isHTMLTag(s.strip()): 28 | return False 29 | return True 30 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDCanNotSetFontFamily.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isFontFamilyName 6 | 7 | class FEDCanNotSetFontFamily(RuleChecker): 8 | 9 | '''{ 10 | "summary":"不允许业务代码设置字体", 11 | "desc":"由于业务代码中随意设置字体,导致字体取值混乱,因此不允许随意在业务代码中设置字体" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'no-font-family' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg = 'can not set font-family for "${selector}"' 18 | 19 | def check(self, rule, config): 20 | if rule.name == 'font-family': 21 | return False 22 | 23 | if rule.name == 'font': 24 | # many fonts 25 | if rule.value.find(',') != -1: 26 | return False 27 | 28 | # one font 29 | splited = rule.value.split(' ') 30 | if isFontFamilyName(splited[len(splited) - 1]): 31 | return False 32 | 33 | return True 34 | -------------------------------------------------------------------------------- /ckstyle/browsers/BinaryRule.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | ''' 5 | 0b111111111 6 | ||||||||| 7 | ||||||||| 8 | |||||||||--ie6 ---------| 9 | ||||||||--ie7 ---------| 10 | |||||||--ie8 ---------| ALLIE 11 | ||||||--ie9+ ---------| 12 | ||||| 13 | |||||--opera 14 | ||||--safari ---------| 15 | |||--firefox | WEBKIT 16 | ||-- chrome ---------| 17 | |-- STD 18 | ''' 19 | 20 | ORIGIN = 0b000000000 21 | 22 | STD = 0b100000000 23 | CHROME = 0b010000000 24 | FIREFOX= 0b001000000 25 | SAFARI = 0b000100000 26 | OPERA = 0b000010000 27 | 28 | WEBKIT = CHROME | SAFARI 29 | 30 | NONEIE = CHROME | SAFARI | OPERA | FIREFOX 31 | 32 | IE9PLUS= 0b000001000 33 | IE8 = 0b000000100 34 | IE7 = 0b000000010 35 | IE6 = 0b000000001 36 | ALLIE = IE9PLUS | IE8 | IE7 | IE6 37 | 38 | NOIE6 = IE9PLUS | IE8 | IE7 | NONEIE 39 | NOIE67 = IE9PLUS | IE8 | NONEIE 40 | NOIE678= IE9PLUS | NONEIE 41 | NONE = 0b000000000 42 | ALL = 0b111111111 -------------------------------------------------------------------------------- /ckstyle/plugins/FEDUseLowerCaseSelector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDUseLowerCaseSelector(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"选择器用小写字母", 10 | "desc":"选择器应该用小写字母, 例如 .demo , 不允许使用大写,例如: .Demo .Test" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'lowercase-selector' 15 | self.errorLevel = ERROR_LEVEL.WARNING 16 | self.errorMsg = 'selector should use lower case, in "${selector}"' 17 | 18 | def check(self, ruleSet, config): 19 | selector = ruleSet.selector 20 | if selector.lower() != selector: 21 | return False 22 | 23 | return True 24 | 25 | def fix(self, ruleSet, config): 26 | # if fix upper to lower, will cause error in HTML(do not do evil) 27 | pass 28 | #selector = ruleSet.selector 29 | #if selector.lower() != selector: 30 | # ruleSet.fixedSelector = ruleSet.fixedSelector.lower() 31 | -------------------------------------------------------------------------------- /tests/unit/fix/CommentForExtraStatement.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _with_comment() 5 | _without_comment() 6 | 7 | def _with_comment(): 8 | css = '''/** 9 | * @descript: topic pages 10 | * @author: ming.zhou 11 | * @date: 2012-12-7 12 | */ 13 | 14 | @import url(dfafdas); 15 | 16 | @import url(dfafdas);''' 17 | 18 | expectedFixed = '''/** 19 | * @descript: topic pages 20 | * @author: ming.zhou 21 | * @date: 2012-12-7 22 | */ 23 | @import url(dfafdas); 24 | 25 | @import url(dfafdas); 26 | ''' 27 | 28 | fixer, msg = doFix(css, '') 29 | equal(msg.strip(), expectedFixed.strip(), 'extra statement with comment is ok') 30 | 31 | def _without_comment(): 32 | css = ''' 33 | 34 | @import url(dfafdas); 35 | 36 | @import url(dfafdas);''' 37 | 38 | expectedFixed = '''@import url(dfafdas); 39 | 40 | @import url(dfafdas); 41 | ''' 42 | 43 | fixer, msg = doFix(css, '') 44 | equal(msg.strip(), expectedFixed.strip(), 'extra statement without comment is ok') 45 | 46 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDHackAttributeInCorrectWay.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isCss3PrefixProp 6 | 7 | class FEDHackAttributeInCorrectWay(RuleChecker): 8 | 9 | '''{ 10 | "summary":"hack属性时的检查", 11 | "desc":"必须使用正确的 hack 方式, 比如 _ * + 等,其他的属性前缀一律不允许" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'hack-prop' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg = '"${name}" is not in correct hacking way in "${selector}"' 18 | 19 | def check(self, rule, config): 20 | if rule.value.find(r'\0') != -1: 21 | return False 22 | 23 | stripped = rule.roughName.strip() 24 | if rule.name == stripped.lower(): 25 | return True 26 | 27 | if isCss3PrefixProp(rule.name): 28 | return True 29 | 30 | if not stripped.startswith('_') and not stripped.startswith('*') and not stripped.startswith('+'): 31 | return False 32 | 33 | return True 34 | -------------------------------------------------------------------------------- /tests/unit/commandline/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.command.ConsoleCommandParser import handleFixStyleCmdArgs, handleCkStyleCmdArgs, handleCompressCmdArgs 9 | 10 | def doCheck(args): 11 | return doCkstyle(args, handleCkStyleCmdArgs) 12 | def doFix(args): 13 | return doCkstyle(args, handleFixStyleCmdArgs) 14 | def doCompress(args): 15 | return doCkstyle(args, handleCompressCmdArgs) 16 | 17 | def doCkstyle(args, handler): 18 | old = sys.stdout 19 | output = realpath('_tmp.txt') 20 | sys.stdout = open(output, 'w') 21 | handler(args) 22 | sys.stdout = old 23 | 24 | result = open(output, 'r').read() 25 | os.remove(output) 26 | return result.strip() 27 | 28 | def realpath(filepath): 29 | dirpath = os.path.realpath(os.path.join(__file__, '../')) 30 | path = os.path.join(dirpath, filepath) 31 | return path -------------------------------------------------------------------------------- /ckstyle/reporter/JsonReporter.py: -------------------------------------------------------------------------------- 1 | from .Reporter import Reporter 2 | from .helper import fill 3 | import json 4 | 5 | class JsonReporter(Reporter): 6 | def __init__(self, checker): 7 | self.checker = checker 8 | self.msg = '' 9 | pass 10 | 11 | def doReport(self): 12 | checker = self.checker 13 | counter = 0 14 | formatter = '%s' 15 | 16 | logs, warns, errors = checker.errors() 17 | if len(logs) == 0 and len(warns) == 0 and len(errors) == 0: 18 | self.setMsg('{}') 19 | return 20 | for error in errors: 21 | error["errorMsg"] = fill(error) 22 | for warn in warns: 23 | warn["errorMsg"] = fill(warn) 24 | for log in logs: 25 | log["errorMsg"] = fill(log) 26 | 27 | self.setMsg('{"errors":%s,"warnings":%s,"logs":%s}' % (json.dumps(errors), json.dumps(warns), json.dumps(logs))) 28 | 29 | def export(self): 30 | return self.msg 31 | 32 | def setMsg(self, msg): 33 | self.msg = msg -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "caption": "Tools", 5 | "children": 6 | [ 7 | { 8 | "id": "fixstyle", 9 | "caption": "Fixstyle", 10 | "command": "fixstyle" 11 | }, 12 | { 13 | "id": "fixstyleSafe", 14 | "caption": "FixstyleSafe", 15 | "command": "fixstylesafe" 16 | }, 17 | { 18 | "id": "fixstyleSingleLine", 19 | "caption": "FixstyleSingleLine", 20 | "command": "fixstylesingleline" 21 | }, 22 | { 23 | "id": "ckstyle", 24 | "caption": "CkStyle", 25 | "command": "ckstyle" 26 | }, 27 | { 28 | "id": "csscompress", 29 | "caption": "CssCompress", 30 | "command": "csscompress" 31 | } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDZIndexShouldInRange.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | import string 6 | from .helper import isCss3PrefixProp 7 | 8 | class FEDZIndexShouldInRange(RuleChecker): 9 | 10 | '''{ 11 | "summary":"z-index取值应符合范围要求", 12 | "desc":"z-index 的取值如果混乱,则会造成层之间的相互覆盖, 13 | 因此 z-index 取值必须符合一定的范围要求,具体要求请参见人人FED CSS编码规范" 14 | }''' 15 | 16 | def __init__(self): 17 | self.id = 'z-index-in-range' 18 | self.errorLevel = ERROR_LEVEL.ERROR 19 | self.errorMsg = 'value of "z-index" is not correct in "${selector}"' 20 | 21 | def check(self, rule, config): 22 | if rule.name != 'z-index': 23 | return True 24 | 25 | zIndex = None 26 | try: 27 | zIndex = string.atoi(rule.value) 28 | except ValueError: 29 | return False 30 | 31 | if zIndex < -1: 32 | return False 33 | 34 | if zIndex > 2100: 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /tests/unit/compress/helper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.realpath(os.path.join(__file__, '../../'))) 4 | for p in os.environ.get('PYTHONPATH', '').split(';'): 5 | sys.path.append(p) 6 | 7 | from asserts import * 8 | from ckstyle.doCssCompress import doCompress 9 | from ckstyle.browsers.BinaryRule import * 10 | 11 | def doCssCompress(fileContent, fileName = ''): 12 | checker, compressed = doCompress(fileContent, fileName) 13 | return compressed 14 | 15 | def doCssCompress2(fileContent, fileName = ''): 16 | checker, compressed = doCompress(fileContent, fileName) 17 | return checker 18 | 19 | def realpath(filepath): 20 | dirpath = os.path.realpath(os.path.join(__file__, '../')) 21 | path = os.path.join(dirpath, filepath) 22 | return path 23 | 24 | def doCssFileCompress(path): 25 | fileContent = open(realpath(path), 'r').read() 26 | return doCssCompress(fileContent, path) 27 | 28 | def doCssFileCompress2(path): 29 | fileContent = open(realpath(path), 'r').read() 30 | return doCssCompress2(fileContent, path) -------------------------------------------------------------------------------- /tests/unit/fix/FixAllInSafeMode.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _singleLine() 5 | _multiLine() 6 | 7 | def _singleLine(): 8 | defaultConfig.safeMode = True 9 | fixer, msg = doFix('.test {width:"100px";color:#DDDDDD;margin:0 auto 0 auto;} .test2 {width:"100px";color:#DDDDDD;margin-top:0;margin-left:auto;margin-right:auto;margin-bottom:0;}', fileName = '', config = defaultConfig) 10 | defaultConfig.safeMode = False 11 | equal(msg, 12 | '''.test { 13 | width: '100px'; 14 | margin: 0 auto; 15 | color: #DDD; 16 | } 17 | 18 | .test2 { 19 | width: '100px'; 20 | margin: 0 auto; 21 | color: #DDD; 22 | }''', 'safe mode true, fix is ok') 23 | 24 | def _multiLine(): 25 | fixer, msg = doFix('.test {width:"100px";color:#DDDDDD;margin:0 auto 0 auto;} .test2 {width:"100px";color:#DDDDDD;margin-top:0;margin-left:auto;margin-right:auto;margin-bottom:0;}', '') 26 | equal(msg, 27 | '''.test, 28 | .test2 { 29 | width: '100px'; 30 | margin: 0 auto; 31 | color: #DDD; 32 | }''', 'default safeMode is false, fix is ok') 33 | -------------------------------------------------------------------------------- /tests/unit/check/FEDFontSizeShouldBePtOrPx.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: font-size unit should be px/pt in ".font-size-is-percent" 6 | 0: font-size unit should be px/pt in ".font-size-is-number" 7 | 0: font-size unit should be px/pt in ".font-size-is-em" 8 | 0: font-size should not be small/medium/large in ".font-size-is-x-small" 9 | 0: font-size should not be small/medium/large in ".font-size-is-x-large" 10 | 0: font-size unit should be px/pt in ".font-size-pt-in-middle" 11 | } 12 | 13 | 14 | .font-size-is-percent { 15 | font-size: 17%; 16 | } 17 | 18 | .font-size-is-number { 19 | font-size: 12.6; 20 | } 21 | 22 | .font-size-is-em { 23 | font-size: 2em; 24 | } 25 | 26 | .font-size-is-x-small { 27 | font-size: x-small; 28 | } 29 | .font-size-is-x-large { 30 | font-size: x-large; 31 | } 32 | 33 | .font-size-pt-in-middle { 34 | font-size: x-pt-smal; 35 | } 36 | 37 | .font-size-is-pt { 38 | font-size: 12pt; 39 | } 40 | 41 | .font-size-is-px { 42 | font-size: 12px; 43 | } 44 | -------------------------------------------------------------------------------- /tests/unit/check/FEDHackRuleSetInCorrectWay.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: not correct hacking way in "@-moz-document url-prefix()" 6 | 0: not correct hacking way in "@-moz-document prefix()" 7 | 0: not correct hacking way in "@media screen and (-webkit-min-device-pixel-ratio:0) and other" 8 | 0: not correct hacking way in "@media all and (-webkit-min-device-pixel-ratio:10), not all and (-webkit-min-device-pixel-ratio:0)" 9 | } 10 | 11 | @-moz-document url-prefix() { 12 | width: 100px; 13 | } 14 | 15 | @-moz-document url-prefix() { 16 | width: 100px; 17 | } 18 | 19 | @-moz-document prefix() { 20 | width: 100px; 21 | } 22 | 23 | 24 | @media screen and (-webkit-min-device-pixel-ratio:0) and other { 25 | width: 100px; 26 | } 27 | 28 | @media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0) { 29 | width: 100px; 30 | } 31 | 32 | @media all and (-webkit-min-device-pixel-ratio:10), not all and (-webkit-min-device-pixel-ratio:0) { 33 | width: 100px; 34 | } 35 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDFixCommentInValue.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDFixCommentInValue(RuleChecker): 7 | 8 | '''{ 9 | "summary":"修复属性中的注释", 10 | "desc":"width:/* fdasfdas */ 100px /* fdafdas */; ==> width:100px;" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'fix-comment-in-value' 15 | self.errorLevel = ERROR_LEVEL.WARNING 16 | self.errorMsg = '' 17 | self.private = True 18 | 19 | def check(self, rule, config): 20 | return True 21 | 22 | 23 | def fix(self, rule, config): 24 | if rule.name == 'expression': 25 | return 26 | value = rule.fixedValue 27 | if value.find('/*') == -1: 28 | return 29 | 30 | splited = value.split('/*') 31 | collector = [] 32 | for x in splited: 33 | tmp = x.split('*/') 34 | if len(tmp) == 1: 35 | collector.append(tmp[0]) 36 | else: 37 | collector.append(tmp[1]) 38 | rule.fixedValue = ''.join(collector) 39 | -------------------------------------------------------------------------------- /ckstyle/reporter/TextReporter.py: -------------------------------------------------------------------------------- 1 | from .Reporter import Reporter 2 | from .helper import fill 3 | 4 | class TextReporter(Reporter): 5 | def __init__(self, checker): 6 | self.checker = checker 7 | self.msgs = [] 8 | 9 | def doReport(self): 10 | checker = self.checker 11 | counter = 0 12 | formatter = '%s %s. %s' 13 | 14 | logs, warns, errors = checker.errors() 15 | if len(logs) == 0 and len(warns) == 0 and len(errors) == 0: 16 | self.appendMsg('aha, no problem') 17 | return 18 | 19 | for error in errors: 20 | counter = counter + 1 21 | self.appendMsg(formatter % ('[ERROR]', counter, fill(error))) 22 | for warn in warns: 23 | counter = counter + 1 24 | self.appendMsg(formatter % (' [WARN]', counter, fill(warn))) 25 | for log in logs: 26 | counter = counter + 1 27 | self.appendMsg(formatter % (' [LOG]', counter, fill(log))) 28 | 29 | def appendMsg(self, msg): 30 | self.msgs.append(msg) 31 | 32 | def export(self): 33 | return '\n'.join(self.msgs) -------------------------------------------------------------------------------- /ckstyle/plugins/FEDMustContainAuthorInfo.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isHTMLTag 6 | 7 | class FEDMustContainAuthorInfo(StyleSheetChecker): 8 | 9 | '''{ 10 | "summary":"需要在文件中添加作者信息", 11 | "desc":"需要在文件中添加作者的信息,本工具认可的作者信息是在文件顶部的注释中添加 @author:xxx" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'add-author' 16 | self.errorMsg_author = 'should add @author in the head of "${file}"' 17 | self.errorMsg_empty = 'empty css file "${file}"' 18 | self.errorMsg = '' 19 | self.errorLevel = ERROR_LEVEL.ERROR 20 | 21 | def check(self, styleSheet, config): 22 | ruleSets = styleSheet.getRuleSets() 23 | if len(ruleSets) == 0: 24 | self.errorMsg = self.errorMsg_empty 25 | return False 26 | 27 | first = ruleSets[0] 28 | 29 | if styleSheet.getFile() != '' and first.comment.find('@author') == -1 and first.comment.find('@renren-inc.com') == -1: 30 | self.errorMsg = self.errorMsg_author 31 | return False 32 | return True 33 | -------------------------------------------------------------------------------- /ckstyle/plugins/combiners/CombinerFactory.py: -------------------------------------------------------------------------------- 1 | from ckstyle.cmdconsole.ConsoleClass import console 2 | 3 | def doCombine(name, props): 4 | pluginName = camelCase(name) + 'Combiner' 5 | pluginClass = NullCombiner 6 | try: 7 | plugin = __import__('ckstyle.plugins.combiners.' + pluginName, fromlist = [pluginName]) 8 | if hasattr(plugin, pluginName): 9 | pluginClass = getattr(plugin, pluginName) 10 | else: 11 | console.error('%s should exist in %s.py' % (pluginName, pluginName)) 12 | except ImportError as e: 13 | pass 14 | instance = pluginClass(name, props) 15 | return instance.combine() 16 | 17 | class NullCombiner(): 18 | def __init__(self, name, props): 19 | pass 20 | def combine(self): 21 | return None, [], False 22 | 23 | def camelCase(name): 24 | splited = name.split('-') 25 | 26 | collector = [] 27 | for x in splited: 28 | collector.append(x.capitalize()) 29 | return ''.join(collector) 30 | 31 | if __name__ == '__main__': 32 | print(doCombine('margin', [ 33 | ['margin', 'margin', '50px auto 0 auto'] 34 | ])) 35 | -------------------------------------------------------------------------------- /tests/unit/fix/FixFEDUseLowerCaseSelector.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _lower() 5 | _upper() 6 | 7 | def _lower(): 8 | fixer, msg = doFix('.test {width:100px;}', '') 9 | styleSheet = fixer.getStyleSheet() 10 | ruleSet = styleSheet.getRuleSets()[0] 11 | equal(ruleSet.fixedSelector, '.test', 'selector lower is ok') 12 | 13 | def _upper(): 14 | # if fix upper to lower, will cause error in HTML, do not do evil 15 | return 16 | fixer, msg = doFix('.TEST {width:100px;}', '') 17 | styleSheet = fixer.getStyleSheet() 18 | ruleSet = styleSheet.getRuleSets()[0] 19 | equal(ruleSet.fixedSelector, '.test', 'selector all upper is ok') 20 | 21 | fixer, msg = doFix('.Test {width:100px;}', '') 22 | styleSheet = fixer.getStyleSheet() 23 | ruleSet = styleSheet.getRuleSets()[0] 24 | equal(ruleSet.fixedSelector, '.test', 'selector one upper is ok') 25 | 26 | fixer, msg = doFix('.Test-WRAPPER {width:100px;}', '') 27 | styleSheet = fixer.getStyleSheet() 28 | ruleSet = styleSheet.getRuleSets()[0] 29 | equal(ruleSet.fixedSelector, '.test-wrapper', 'selector upper with - is ok') 30 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDReplaceBorderZeroWithBorderNone.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDReplaceBorderZeroWithBorderNone(RuleChecker): 7 | 8 | '''{ 9 | "summary":"用border:none替换border:0", 10 | "desc":"border:0 实际上是有border的,只不过宽度为0, 而 border:none; 11 | 是根本没有border的,对于浏览器来说后者的效率高,但是要注意,后者的代码长度稍微长一些。" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'no-border-zero' 16 | self.errorLevel = ERROR_LEVEL.WARNING 17 | self.errorMsg_borderWidth = 'replace "border-width: 0" with "border-width: none" in "${selector}"' 18 | self.errorMsg_border = 'replace "border: 0" with "border: none" in "${selector}"' 19 | self.errorMsg = '' 20 | 21 | def check(self, rule, config): 22 | if rule.name == 'border' and rule.value == '0': 23 | self.errorMsg = self.errorMsg_border 24 | return False 25 | 26 | if rule.name == 'border-width' and rule.value == '0': 27 | self.errorMsg = self.errorMsg_borderWidth 28 | return False 29 | 30 | return True 31 | -------------------------------------------------------------------------------- /tests/demo/demo-compress.css: -------------------------------------------------------------------------------- 1 | .test1 { 2 | width: 100px; 3 | height: 200px; 4 | *display: none; 5 | border: 1px solid #FFFFFF; 6 | _display: inline-block; 7 | margin: 10px; 8 | margin-top: 20px; 9 | } 10 | 11 | .test2 { 12 | *display: none; 13 | width: 100px; 14 | border: 1px solid #FFF; 15 | height: 200px; 16 | _display: inline-block; 17 | margin: 20px 10px 10px; 18 | } 19 | 20 | .test3 { 21 | margin: 0 10px 20px; 22 | border: 1px solid #fff; 23 | width: 100px; 24 | height: 200px; 25 | *display: none; 26 | _display: inline-block; 27 | margin-top: 20px; 28 | margin-left: 10px; 29 | margin-right: 10px; 30 | margin-bottom: 10px; 31 | } 32 | 33 | .test4 { 34 | border: 1px solid #ffffff; 35 | *display: none; 36 | width: 100px; 37 | height: 200px; 38 | margin: 10px; 39 | _display: inline-block; 40 | margin-top: 20px; 41 | } 42 | 43 | .test5 { 44 | margin: 10px; 45 | margin-top: 20px; 46 | width: 100px; *display: none; height: 200px; 47 | border: 1px solid #ffffff; 48 | _display: inline-block; 49 | margin-left: 10px; 50 | } 51 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoSimpleNumberInSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: do not simply use 1,2,3 as selector(use v1/step1/item1), in ".class1" 6 | 1: do not simply use 1,2,3 as selector(use v1/step1/item1), in ".class2" 7 | 1: do not simply use 1,2,3 as selector(use v1/step1/item1), in ".class6" 8 | 1: do not simply use 1,2,3 as selector(use v1/step1/item1), in ".fdjas6" 9 | 1: do not simply use 1,2,3 as selector(use v1/step1/item1), in ".test1000" 10 | 11 | 1: ".class1, .class2, .class6, .class .ok, .v6, .class .step3, .fdjas6, .test1000, .test-item111" contains the same rules in "FEDNoSimpleNumberInSelector.css" 12 | } 13 | 14 | .class1 { 15 | width: 100px; 16 | } 17 | 18 | .class2 { 19 | width: 100px; 20 | } 21 | 22 | .class6 { 23 | width: 100px; 24 | } 25 | 26 | .class .ok { 27 | width: 100px; 28 | } 29 | 30 | .v6 { 31 | width: 100px; 32 | } 33 | 34 | .class .step3 { 35 | width: 100px; 36 | } 37 | 38 | .fdjas6 { 39 | width: 100px; 40 | } 41 | 42 | .test1000 { 43 | width: 100px; 44 | } 45 | 46 | .test-item111 { 47 | width: 100px; 48 | } -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoAppearanceNameInSelector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import existsAppearanceWords 6 | 7 | class FEDNoAppearanceNameInSelector(RuleSetChecker): 8 | 9 | '''{ 10 | "summary":"选择器中避免表现相关的词汇", 11 | "desc":"避免将在selector中出现 .red .left 等描述性词汇, 12 | 用具体的实际意义来代替,比如 .error .sidebar " 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'no-appearance-word-in-selector' 17 | self.errorLevel = ERROR_LEVEL.WARNING 18 | self.errorMsg_origin = 'should not use appearance word "%s" in "${selector}"' 19 | self.errorMsg = '' 20 | 21 | def check(self, ruleSet, config): 22 | selector = ruleSet.selector.lower() 23 | 24 | if selector.find('@media') != -1: 25 | return True 26 | if selector.find('@-moz-document') != -1: 27 | return True 28 | 29 | word = existsAppearanceWords(selector) 30 | if word is not None: 31 | self.errorMsg = self.errorMsg_origin % word 32 | return False 33 | 34 | return True 35 | -------------------------------------------------------------------------------- /ckstyle/plugins/validators/ValidatorFactory.py: -------------------------------------------------------------------------------- 1 | from ckstyle.cmdconsole.ConsoleClass import console 2 | 3 | def doValidate(name, value): 4 | pluginName = camelCase(name) + 'Validator' 5 | pluginClass = NullValidator 6 | try: 7 | plugin = __import__('ckstyle.plugins.validators.' + pluginName, fromlist = [pluginName]) 8 | #plugin = __import__(pluginName, fromlist = [pluginName]) 9 | if hasattr(plugin, pluginName): 10 | pluginClass = getattr(plugin, pluginName) 11 | else: 12 | console.error('%s should exist in %s.py' % (pluginName, pluginName)) 13 | except ImportError as e: 14 | pass 15 | instance = pluginClass(name, value) 16 | return instance.validate() 17 | 18 | class NullValidator(): 19 | def __init__(self, name, value): 20 | pass 21 | def validate(self): 22 | return True, None 23 | 24 | def camelCase(name): 25 | splited = name.split('-') 26 | 27 | collector = [] 28 | for x in splited: 29 | collector.append(x.capitalize()) 30 | return ''.join(collector) 31 | 32 | if __name__ == '__main__': 33 | print(doValidate('margin', '10 10 10 10')) 34 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoStarInSelector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDNoStarInSelector(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"不要在选择器中使用星号", 10 | "desc":"禁止在选择器中加入*来选择所有元素,例如:
11 |
12 | *html *+html *:not等几种特殊hack除外" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'no-star-in-selector' 17 | self.errorLevel = ERROR_LEVEL.ERROR 18 | self.errorMsg = 'please remove low performance selector "*" from "${selector}"' 19 | 20 | def check(self, ruleSet, config): 21 | selector = ruleSet.selector 22 | if selector.find('*') == -1: 23 | return True 24 | 25 | replaced = selector.replace(' ', '') 26 | if replaced.startswith('*html') or replaced.startswith('*+html'): 27 | return True 28 | 29 | if replaced.find('*:not') != -1: 30 | return True 31 | 32 | # give it to FEDHighPerformanceSelector.py 33 | if replaced.find('*=') != -1 and len(replaced.split('*')) == 2: 34 | return True 35 | 36 | return False 37 | -------------------------------------------------------------------------------- /tests/unit/check/FEDUseLowerCaseProp.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: "WIDTH" should use lower case, in ".name-upper" 6 | 1: "HEIGHT" should use lower case, in ".name-upper-second-attr" 7 | 1: value of "width" should use lower case, in ".value-upper" 8 | 0: can not set font-family for ".value-upper-font-family" 9 | 1: value of "font" should use lower case, in ".value-upper-font" 10 | 0: can not set font-family for ".value-upper-font" 11 | } 12 | 13 | .is-correct-lower { 14 | width: 100px; 15 | height: 200px; 16 | } 17 | 18 | .name-upper { 19 | WIDTH: 100px; 20 | } 21 | 22 | .name-upper-second-attr { 23 | width: 100px; 24 | HEIGHT: 10px; 25 | } 26 | 27 | .value-upper { 28 | width: 100PX; 29 | } 30 | 31 | .value-upper-color-text { 32 | color: RED; 33 | } 34 | 35 | .value-uppper-color { 36 | color: #FFF; 37 | } 38 | 39 | .value-upper-background { 40 | background: #FFF url('xxxx.png') no-repeat left top; 41 | } 42 | 43 | .value-upper-font-family { 44 | font-family: 'Microsoft Yahei'; 45 | } 46 | 47 | .value-upper-font { 48 | font: ITALIC bold 12px/30px 'Courier New', Georgia, serif; 49 | } 50 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoSimpleNumberInSelector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | import re 6 | pattern = re.compile('\d+') 7 | 8 | class FEDNoSimpleNumberInSelector(RuleSetChecker): 9 | 10 | '''{ 11 | "summary":"不要在选择器中使用简单数字", 12 | "desc":"在业务代码的css中,选择器中不要使用简单的 1, 2, 3 来进行命名,下面的命名方式就是错误的:
13 | .test1 .main1,但是允许使用 v1 step1 item1 14 | 来代表版本、步骤、第几个元素的意思" 15 | }''' 16 | 17 | def __init__(self): 18 | self.id = 'number-in-selector' 19 | self.errorLevel = ERROR_LEVEL.WARNING 20 | self.errorMsg = 'do not simply use 1,2,3 as selector(use v1/step1/item1), in "${selector}"' 21 | 22 | def check(self, ruleSet, config): 23 | selector = ruleSet.selector 24 | 25 | if selector.find('@media') != -1: 26 | return True 27 | 28 | found = pattern.findall(selector) 29 | for x in found: 30 | if selector.find('v' + x) == -1 and selector.find('step' + x) == -1 and selector.find('item' + x) == -1 and selector.find('h' + x) == -1 : 31 | return False 32 | return True 33 | -------------------------------------------------------------------------------- /ckstyle/entity/NestedStatement.py: -------------------------------------------------------------------------------- 1 | from .EntityUtil import Cleaner, ALL 2 | 3 | class NestedStatement(): 4 | def __init__(self, selector, statement, comments, styleSheet = None): 5 | self.extra = True 6 | self.nested = True 7 | self.selector = selector.strip() 8 | self.statement = statement.strip() 9 | self.roughStatement = statement 10 | self.comments = comments.strip() 11 | self.styleSheet = styleSheet 12 | 13 | self.fixedSelector = '' 14 | self.fixedStatement = '' 15 | 16 | self.browser = ALL 17 | self.toBeUsed = {} 18 | 19 | def rebase(self): 20 | self.fixedSelector = '' 21 | self.fixedStatement = '' 22 | 23 | def compress(self, browser = ALL): 24 | if not self.browser & browser: 25 | return '' 26 | return self.fixedSelector + self._compressedStatement() 27 | 28 | def fixed(self, config): 29 | return self.fixedSelector + ' {\n ' + '\n '.join(self.fixedStatement.split('\n')) + '\n}' 30 | 31 | def _compressedStatement(self): 32 | return '{' + Cleaner.clean(self.fixedStatement) + '}' 33 | 34 | def __str__(self): 35 | return '%s' % self.statement 36 | -------------------------------------------------------------------------------- /ckstyle/browsers/Analyser.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .BinaryRule import * 5 | 6 | basic = { 7 | 'ie6' : IE6, 8 | 'ie7' : IE7, 9 | 'ie8' : IE8, 10 | 'ie9' : IE9PLUS, 11 | 'ie10': IE9PLUS, 12 | 'chrome' : CHROME, 13 | 'firefox' : FIREFOX, 14 | 'opera' : OPERA, 15 | 'safari' : SAFARI, 16 | 'std' : STD | NONEIE 17 | } 18 | 19 | mapping = { 20 | 'ie' : ALLIE, 21 | 'ie9plus' : IE9PLUS, 22 | 'std' : STD | NONEIE 23 | } 24 | 25 | mapping.update(basic) 26 | 27 | allBrowsers = ','.join([x for x in mapping.keys() if x != 'webkit' and x != 'ie9plus']) 28 | 29 | def analyse(text): 30 | if not text or text == '' or text=='none' or text == 'false': 31 | return None 32 | if text == 'all': 33 | text = allBrowsers 34 | text = text.lower() 35 | splited = text.split(',') 36 | browsers = {} 37 | 38 | for browser in splited: 39 | if mapping.has_key(browser): 40 | # 不管如何选择,STD的总是要显示的 41 | browsers[browser] = mapping[browser] | STD 42 | return browsers 43 | 44 | def whatIs(code): 45 | result = [] 46 | for name, value in basic.items(): 47 | if value & code: 48 | result.append(name) 49 | 50 | return ','.join(result) 51 | 52 | if __name__ == '__main__': 53 | print(analyse('ie6,std')) 54 | print(whatIs(FIREFOX)) -------------------------------------------------------------------------------- /tests/unit/check/FEDMultiLineBraces.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: should "enter" after the opening brace in ".multi-no-enter-before-open-brace" 6 | 2: should "enter" before the closing brace in ".multi-brace-no-enter" 7 | 2: extra "space" after the opening brace in ".multi-space-after-brace" 8 | 2: every name/value should in single line in ".multi-brace-no-enter-another" 9 | 2: should have (only) one "space" before value of "width" in ".multi-brace-no-enter" 10 | 2: should have (only) one "space" before value of "width" in ".multi-brace-no-enter-another" 11 | 12 | 1: ".multi-a, .multi-no-enter-before-open-brace, .multi-space-after-brace, .multi-brace-no-enter, .multi-brace-no-enter-another" contains the same rules in "FEDMultiLineBraces.css" 13 | } 14 | 15 | .multi-a { 16 | width: 100px; 17 | height: 100px; 18 | } 19 | 20 | .multi-no-enter-before-open-brace { width: 100px; 21 | height: 100px; 22 | } 23 | 24 | .multi-space-after-brace { 25 | width: 100px; 26 | height: 100px; 27 | } 28 | 29 | .multi-brace-no-enter { 30 | width:100px; 31 | height: 100px; } 32 | 33 | .multi-brace-no-enter-another { 34 | width:100px; height: 100px; } 35 | -------------------------------------------------------------------------------- /ckstyle/plugins/Base.py: -------------------------------------------------------------------------------- 1 | class ERROR_LEVEL: 2 | ERROR = 0 3 | WARNING = 1 4 | LOG = 2 5 | 6 | class Checker(): 7 | def __init__(self): 8 | self.errorLevel = ERROR_LEVEL.LOG 9 | self.errorMsg = '_default_msg' 10 | def check(self, xxx, config): 11 | pass 12 | def getMsg(self): 13 | return self.errorMsg 14 | def getLevel(self): 15 | return self.errorLevel 16 | 17 | class RuleChecker(Checker): 18 | def __init__(self): 19 | self.errorLevel = ERROR_LEVEL.LOG 20 | self.errorMsg = '' 21 | def check(self, rule, config): 22 | return True 23 | 24 | class RuleSetChecker(Checker): 25 | def __init__(self): 26 | self.errorLevel = ERROR_LEVEL.LOG 27 | self.errorMsg = '' 28 | def check(self, ruleSet, config): 29 | return True 30 | 31 | class StyleSheetChecker(Checker): 32 | def __init__(self): 33 | self.errorLevel = ERROL_LEVEL.LOG 34 | self.errorMsg = '' 35 | def check(self, styleSheet, config): 36 | return True 37 | 38 | class ExtraChecker(Checker): 39 | def __init__(self): 40 | self.errorLevel = ERROR_LEVEL.LOG 41 | self.errorMsg = '' 42 | def check(self, ruleSet, config): 43 | return True -------------------------------------------------------------------------------- /tests/unit/check/FEDSingleLineSpaces.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: should have one "space" before "width" in ".single-line-no-space" 6 | 2: should have "only one space" after the opening brace in ".single-line-no-space" 7 | 2: should have one "space" before "height" in ".single-line-no-space" 8 | 2: should not have "space" after "width" in ".extra-space-end" 9 | 2: should have one "space" before value of "width" in ".extra-space-before-value" 10 | 2: found extra "space" after value of "width" in ".extra-space-after-value" 11 | 1: each rule in ".extra-space-after-value" need semicolon in the end, "width" has not 12 | 13 | 1: ".extra-space-end, .extra-space-before-value, .extra-space-after-value" contains the same rules in "FEDSingleLineSpaces.css" 14 | 1: ".correct-single-line-styles, .single-line-no-space" contains the same rules in "FEDSingleLineSpaces.css" 15 | } 16 | 17 | .correct-single-line-styles { width: 100px; height: 100px; } 18 | .single-line-no-space {width:100px;height:100px;} 19 | .extra-space-end { width :100px; } 20 | .extra-space-before-value { width:100px; } 21 | .extra-space-after-value { width: 100px ; } 22 | .extra-space-after-value { width: 100px } 23 | -------------------------------------------------------------------------------- /tests/unit/parser/NestedStatement.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _withoutEnter() 5 | 6 | def _withoutEnter(): 7 | parser = CssParser("@keyframes 'blala' {10% {width: 100px;} 20% {width: 300px;}}@keyframes 'blala2' {10% {width: 200px;}}.test {_width : 100px;}") 8 | parser.doParse() 9 | ok(parser is not None, 'parser is not None') 10 | ok(parser.styleSheet is not None, 'parser.styleSheet is not None') 11 | equal(len(parser.styleSheet.getRuleSets()), 3, 'three rule set') 12 | 13 | styleSheet = parser.styleSheet 14 | first = styleSheet.getRuleSets()[0] 15 | ok(first.extra, 'keyframes is extra') 16 | equal(first.selector, "@keyframes 'blala'", 'it is @keyframes') 17 | equal(first.statement, '10% {width: 100px;} 20% {width: 300px;}', 'statement is ok') 18 | 19 | second = styleSheet.getRuleSets()[1] 20 | ok(second.extra, 'keyframes is extra') 21 | equal(second.selector, "@keyframes 'blala2'", 'it is @keyframes') 22 | equal(second.statement, '10% {width: 200px;}', 'statement is ok') 23 | 24 | rule = styleSheet.getRuleSets()[2] 25 | ok(not rule.extra, 'not extra') 26 | equal(rule.selector, '.test', 'selector is ok') 27 | equal(rule.getRuleByName('width').value, '100px', 'value is ok') 28 | -------------------------------------------------------------------------------- /tests/unit/commandline/Try.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | res = doCheck([' ', 'check', '-p', realpath('./_test.css')]) 5 | res = res.find('[ERROR] 1. should add @author in the head of') 6 | equal(res, 0, 'check by cmd line is ok') 7 | 8 | res = doFix([' ', 'fix', '-p', realpath('./_test.css')]) 9 | expect = '''.test { 10 | width: 100px; 11 | }''' 12 | equal(res, expect, 'fix by cmd line is ok') 13 | 14 | 15 | res = doCompress([' ', 'compress', '-p', realpath('./_test.css')]) 16 | expect = '''.test{width:100px}''' 17 | equal(res, expect, 'compress by cmd line is ok') 18 | 19 | res = doCompress([' ', 'compress', '-p', realpath('./_test_browsers.css')]) 20 | expect = '''.test{width:100px}.test[prop]{width:100px}''' 21 | equal(res, expect, 'compress by cmd line is ok') 22 | 23 | res = doCompress([' ', 'compress', '-p', '--browsers=ie6', realpath('./_test_browsers.css')]) 24 | expect = '''.test{width:100px}''' 25 | equal(res, expect, 'compress by cmd line ie6 is ok') 26 | 27 | res = doCompress([' ', 'compress', '-p', '--browsers=ie7', realpath('./_test_browsers.css')]) 28 | expect = '''.test{width:100px}.test[prop]{width:100px}''' 29 | equal(res, expect, 'compress by cmd line ie7 is ok') -------------------------------------------------------------------------------- /ckstyle/plugins/FEDFixNestedStatement.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDFixNestedStatement(ExtraChecker): 7 | 8 | '''{ 9 | "summary":"修复嵌套的CSS", 10 | "desc":"@keyframes, @media之类的" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'fix-nested-ruleset' 15 | self.errorLevel = ERROR_LEVEL.ERROR 16 | self.errorMsg = '' 17 | self.private = True 18 | 19 | def check(self, ruleSet, config): 20 | return True 21 | 22 | def fix(self, ruleSet, config): 23 | if not ruleSet.nested: 24 | return 25 | ruleSet.fixedSelector = ruleSet.fixedSelector.replace('"', '\'') 26 | statement = ruleSet.fixedStatement 27 | if (hasattr(config, 'operation') and getattr(config, 'operation') == 'compress'): 28 | from ckstyle.doCssCompress import prepare 29 | checker = prepare(statement, '', config) 30 | # 嵌套的CSS,如果是压缩,也需要精简 31 | msg = checker.doCompress(config._curBrowser) 32 | ruleSet.fixedStatement = msg 33 | else: 34 | from ckstyle.doCssFix import doFix 35 | checker, msg = doFix(statement, '', config) 36 | ruleSet.fixedStatement = msg 37 | -------------------------------------------------------------------------------- /tests/unit/combiner/Try.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | msg = doCssFileCompress('_test.css') 5 | equal(msg, '@import (url-here);.test,.test2,.test3,.test4,.test5{_width:100px;*height:100px}.test6{display:none;_width:100px;*height:100px}', 'totally compressed') 6 | 7 | msg = doCssFileCompress('_test_different_order.css') 8 | equal(msg, '.test1,.test2,.test3,.test4,.test5{*display:none;_display:inline-block;width:100px;height:200px;border:1px solid #FFF}', 'totally compressed') 9 | 10 | msg = doCssFileCompress('_with_margin.css') 11 | equal(msg, '.test,.test2,.test3,.test4,.test5{_width:100px;*height:100px;margin:20px 10px 10px}.test6{display:none;_width:100px;*height:100px}', 'margin compress ok') 12 | 13 | msg = doCssFileCompress('_just_margin.css') 14 | equal(msg, '.test,.test2,.test3,.test4{margin:20px 10px 10px}', 'just margin compress ok') 15 | 16 | msg = doCssFileCompress('_with_padding.css') 17 | equal(msg, '.test,.test2,.test3,.test4,.test5{_width:100px;*height:100px;padding:20px 10px 10px}.test6{display:none;_width:100px;*height:100px}', 'padding compress ok') 18 | 19 | msg = doCssFileCompress('_just_padding.css') 20 | equal(msg, '.test,.test2,.test3,.test4{padding:20px 10px 10px}', 'just padding compress ok') 21 | -------------------------------------------------------------------------------- /ckstyle/reporter/helper.py: -------------------------------------------------------------------------------- 1 | 2 | def fill(obj): 3 | def fillRuleSet( obj): 4 | errorMsg = obj["errorMsg"] 5 | if errorMsg.find('${selector}') == -1: 6 | errorMsg = errorMsg + ' (from "' + obj["selector"] + '")' 7 | else: 8 | errorMsg = errorMsg.replace('${selector}', obj["selector"]) 9 | return errorMsg 10 | 11 | def fillStyleSheet( obj): 12 | errorMsg = obj["errorMsg"] 13 | if errorMsg.find('${file}') == -1: 14 | errorMsg = errorMsg + ' (from "' + obj["file"] + '")' 15 | else: 16 | errorMsg = errorMsg.replace('${file}', obj["file"]) 17 | return errorMsg 18 | 19 | def fillRule(obj): 20 | errorMsg = obj["errorMsg"] 21 | if errorMsg.find('${selector}') == -1: 22 | errorMsg = errorMsg + ' (from "' + obj["selector"] + '")' 23 | else: 24 | errorMsg = errorMsg.replace('${selector}', obj["selector"]) 25 | errorMsg = errorMsg.replace('${name}', obj["name"]) 26 | errorMsg = errorMsg.replace('${value}', obj["value"]) 27 | return errorMsg 28 | 29 | level = obj["level"] 30 | if level == 'rule': 31 | return fillRule(obj) 32 | elif level == 'ruleset': 33 | return fillRuleSet(obj) 34 | elif level == 'stylesheet': 35 | return fillStyleSheet(obj) 36 | return obj["errorMsg"] -------------------------------------------------------------------------------- /tests/unit/fix/ExtraCss3Prefix.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _not_listed() 5 | _both_not_listed() 6 | 7 | def _both_not_listed(): 8 | css = '''html { 9 | -webkit-hyphens: auto; 10 | -moz-hyphens: auto; 11 | -ms-hyphens: auto; 12 | -o-hyphens: auto; 13 | hyphens: auto; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | }''' 18 | 19 | expectedFixed = '''html { 20 | -webkit-hyphens: auto; 21 | -moz-hyphens: auto; 22 | -ms-hyphens: auto; 23 | -o-hyphens: auto; 24 | hyphens: auto; 25 | -webkit-user-select: none; 26 | -moz-user-select: none; 27 | -ms-user-select: none; 28 | }''' 29 | 30 | fixer, msg = doFix(css, '') 31 | equal(msg.strip(), expectedFixed.strip(), 'both css3 prop not listed is ok') 32 | 33 | def _not_listed(): 34 | css = '''.html { 35 | -moz-box-sizing: 1px; 36 | -webkit-box-sizing: 1px; 37 | box-sizing: 1px; 38 | }''' 39 | 40 | expectedFixed = '''.html { 41 | -webkit-box-sizing: 1px; 42 | -moz-box-sizing: 1px; 43 | box-sizing: 1px; 44 | }''' 45 | 46 | fixer, msg = doFix(css, '') 47 | equal(msg.strip(), expectedFixed.strip(), 'css3 prefix box-sizing is ok') 48 | -------------------------------------------------------------------------------- /tests/unit/check/FEDCombineInToOne.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 1: should combine "border-width,border-style" to "border" in ".test-border" 6 | 1: should combine "background-color,background-image,background-repeat" to "background" in ".test-background" 7 | 1: should combine "padding-top,padding-bottom" to "padding" in ".test-padding" 8 | 1: should combine "margin,margin-bottom" to "margin" in ".test-margin" 9 | 1: should combine "font-size,font-family,font-style" to "font" in ".test-font" 10 | 0: can not set font-family for ".test-font" 11 | } 12 | 13 | .test-border { 14 | border-width: 1px; 15 | border-style: solid; 16 | } 17 | 18 | .test-background { 19 | background-color: red; 20 | background-image: url(xxxx); 21 | background-repeat: no-repeat; 22 | } 23 | 24 | .test-padding { 25 | padding-top: 10px; 26 | padding-bottom: 12px; 27 | } 28 | 29 | .test-margin { 30 | margin: 10px; 31 | margin-bottom: 10px; 32 | } 33 | 34 | .test-font { 35 | font-size: 12px; 36 | font-family: 'Microsoft Yahei'; 37 | font-style: italic; 38 | } 39 | 40 | .test-font-only-two { 41 | font-size: 12px; 42 | font-style: italic; 43 | } 44 | 45 | .help-message-btn { 46 | padding: 5px 0; 47 | *padding: 6px 0 3px; 48 | } -------------------------------------------------------------------------------- /tests/unit/entity/ExtraStatement.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _basic() 5 | _opm() 6 | _other() 7 | 8 | def _basic(): 9 | stmt = ExtraStatement('@import', '@import url("fjdaslkjfdsa")', None) 10 | ok(stmt.extra, '@import is extra statement') 11 | equal(stmt.operator, '@import', 'operator is @important') 12 | equal(stmt.statement, '@import url("fjdaslkjfdsa")', 'statement is ok') 13 | equal(stmt.styleSheet, None, 'no style sheet') 14 | ok(stmt.isImport(), 'yes, it is import statement') 15 | ok(not stmt.isOpmOperator(), 'no, it is not opm operator') 16 | 17 | def _opm(): 18 | stmt = ExtraStatement('@-css-compiler-xxx', '@-css-compiler-xxx fdjafdjafda;', None) 19 | ok(stmt.isOpmOperator(), 'yes, it is opm operator') 20 | 21 | stmt = ExtraStatement('@-css-compiler-xxx', '@-css-compiler-xxx fdjafdjafda;', None) 22 | ok(stmt.isOpmOperator(), 'yes, it is opm operator') 23 | 24 | def _other(): 25 | stmt = ExtraStatement('@namspace', '@namspace fdjafdjafda;', None) 26 | ok(not stmt.isOpmOperator(), 'no, it is not opm operator') 27 | ok(not stmt.isImport(), 'no, it is not import') 28 | 29 | stmt = ExtraStatement('@charset', '@charset utf-8;', None) 30 | ok(not stmt.isOpmOperator(), 'no, it is not opm operator') 31 | ok(not stmt.isImport(), 'no, it is not import') 32 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDSingleLineSelector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDSingleLineSelector(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"单行的选择器检查", 10 | "desc":"单行的选择器检查内容,请参考多行选择器检查和人人FED CSS编码规范" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'single-line-selector' 15 | self.errorLevel = ERROR_LEVEL.LOG 16 | self.errorMsg_noEnterInSingleSelector = 'should not "enter" at the end of "${selector}"' 17 | self.errorMsg_multiSelectorBeforeSemicolon = 'should not have "space" after semicolon in "${selector}"' 18 | self.errorMsg_shouldNotStartsWithSpace = 'should start with "space" in "${selector}"' 19 | self.errorMsg = '' 20 | 21 | def check(self, ruleSet, config): 22 | selector = ruleSet.roughSelector 23 | if selector.find(',') != -1: 24 | return True 25 | 26 | if selector.lstrip().find('\n') != -1: 27 | self.errorMsg = self.errorMsg_noEnterInSingleSelector 28 | return False 29 | 30 | splited = selector.split('\n') 31 | realSelector = splited[len(splited) - 1] 32 | 33 | if realSelector.startswith(' '): 34 | self.errorMsg = self.errorMsg_shouldNotStartsWithSpace 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDDoNotSetStyleForSimpleSelector.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isSimpleSelector 6 | 7 | class FEDDoNotSetStyleForSimpleSelector(RuleSetChecker): 8 | 9 | '''{ 10 | "summary":"不要为简单选择器设置样式", 11 | "desc":"一些简单的选择器,比如:
12 | .nav/.list/.content
13 | 非常容易造成属性的相互覆盖,因此在写这样的选择器时,最好加上前缀,比如
14 | .module-name .nav

15 | 工具现有的简单选择器判断,请参考:
16 | plugins/helper.py" 17 | }''' 18 | 19 | def __init__(self): 20 | self.id = 'no-style-for-simple-selector' 21 | self.errorLevel = ERROR_LEVEL.ERROR 22 | self.errorMsg_rough = 'should not set style for "%s" in "${selector}"' 23 | self.errorMsg = '' 24 | 25 | def check(self, ruleSet, config): 26 | selector = ruleSet.selector.lower() 27 | 28 | if selector.find('@media') != -1: 29 | return True 30 | 31 | if selector.find('@-moz-document') != -1: 32 | return True 33 | 34 | selectors = selector.split(',') 35 | for s in selectors: 36 | s = s.strip() 37 | if isSimpleSelector(s): 38 | self.errorMsg = self.errorMsg_rough % s 39 | return False 40 | return True 41 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoZeroBeforeDot.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDNoZeroBeforeDot(RuleChecker): 7 | 8 | '''{ 9 | "summary":"删除0.x前面的0", 10 | "desc":" 0.xxx 前面的 0 是可以删除的,以实现更好的压缩。例如
11 | 0.3px ==> .3px

12 | rgba(0,0,0,0.3)
13 | ==>
14 | rgba(0,0,0,.3)" 15 | }''' 16 | 17 | def __init__(self): 18 | self.id = 'no-zero-before-dot' 19 | self.errorLevel = ERROR_LEVEL.WARNING 20 | self.errorMsg = 'zero should be removed when meet 0.xxx in "${selector}"' 21 | 22 | def check(self, rule, config): 23 | value = rule.value 24 | 25 | if self._startsWithZeroDot(value): 26 | return False 27 | 28 | values = rule.value.split(' ') 29 | for v in values: 30 | if self._startsWithZeroDot(v.strip()): 31 | return False 32 | 33 | return True 34 | 35 | def fix(self, rule, config): 36 | fixedValue = rule.fixedValue 37 | for v in fixedValue.split(' '): 38 | if self._startsWithZeroDot(v): 39 | rule.fixedValue = rule.fixedValue.replace(v, v[1:]) 40 | 41 | def _startsWithZeroDot(self, value): 42 | return value.startswith('0.') 43 | -------------------------------------------------------------------------------- /tests/unit/check/FEDCommentLengthLessThan80.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: comment for ".test-comment-length" length should less than 80 per line 6 | 2: comment for ".test-chn-length" length should less than 80 per line 7 | 2: comment for ".test-chn-and-eng-length" length should less than 80 per line 8 | 2: comment for ".test-comment-multiline-length" length should less than 80 per line 9 | 1: ".test-comment-length, .test-chn-length, .test-chn-and-eng-length, .test-comment-multiline-length, .correct-comment" contains the same rules in "FEDCommentLengthLessThan80.css" 10 | } 11 | 12 | /** fdasfdsaffffffffffffffffffffffffffffffffffffffffffffffffffffffffftested code */ 13 | .test-comment-length { 14 | width: 100px; 15 | } 16 | 17 | /* 中文字符中文字符中文字符中文字符中文字符中文字符中文字符中文字符中文字符中文字符 */ 18 | .test-chn-length { 19 | width: 100px; 20 | } 21 | 22 | /* fdasjklfjdlskafdlasjfldsajfd符中文字符中文字符中文字符中文字符中文字符中文字符 */ 23 | .test-chn-and-eng-length { 24 | width: 100px; 25 | } 26 | 27 | /* 28 | * fdjlfdfdsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 29 | * fffffffffffffffffffjfkldasjflkda 30 | */ 31 | .test-comment-multiline-length { 32 | width: 100px; 33 | } 34 | 35 | /* 36 | * fjdklajfdlsjflkdsjkfdsjakl 37 | */ 38 | .correct-comment { 39 | width: 100px; 40 | } 41 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDFontSizeShouldBePtOrPx.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDFontSizeShouldBePtOrPx(RuleChecker): 7 | 8 | '''{ 9 | "summary":"字体的单位必须用px或pt", 10 | "desc":"字体的单位可以有很多种,比如 px pt em % 等等,为了统一取值,统一要求为 px/pt , 例如:
11 | font-size: 12px;
12 | font-size: 14pt;" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'font-unit' 17 | self.errorLevel = ERROR_LEVEL.ERROR 18 | self.errorMsg_ptOrPx = 'font-size unit should be px/pt in "${selector}"' 19 | self.errorMsg_xsmall = 'font-size should not be small/medium/large in "${selector}"' 20 | self.errorMsg = '' 21 | 22 | def check(self, rule, config): 23 | if rule.name != 'font-size': 24 | return True 25 | 26 | value = rule.value 27 | if value.find('small') != -1 or value.find('medium') != -1 or value.find('large') != -1: 28 | self.errorMsg = self.errorMsg_xsmall 29 | return False 30 | 31 | if value == '0': 32 | return True 33 | 34 | if value.endswith('pt'): 35 | return True 36 | 37 | if value.endswith('px'): 38 | return True 39 | 40 | self.errorMsg = self.errorMsg_ptOrPx 41 | return False 42 | -------------------------------------------------------------------------------- /tests/unit/compress/_selectors_from_kimblim.css: -------------------------------------------------------------------------------- 1 | div {width:100px} 2 | div span {width:100px} 3 | :link {width:100px} 4 | div:active {width:100px} 5 | div:visited {width:100px} 6 | div:first-line {width:100px} 7 | div:first-letter {width:100px} 8 | div.classname {width:100px} 9 | div#id {width:100px} 10 | .classname.classname {width:100px} 11 | * {width:100px} 12 | div > span {width:100px} 13 | div:first-child {width:100px} 14 | div:hover {width:100px} 15 | div:focus {width:100px} 16 | div + span {width:100px} 17 | div[attr] {width:100px} 18 | div[attr="name"] {width:100px} 19 | div[attr~="name"] {width:100px} 20 | div:before {width:100px} 21 | div:after {width:100px} 22 | div ~ span {width:100px} 23 | div[attr^="name"] {width:100px} 24 | div[attr$="name"] {width:100px} 25 | div[attr*="name"] {width:100px} 26 | div[attr|="name"] {width:100px} 27 | div:root {width:100px} 28 | div:nth-of-type {width:100px} 29 | div:nth-last-of-type {width:100px} 30 | div:first-of-type {width:100px} 31 | div:last-of-type {width:100px} 32 | div:only-of-type {width:100px} 33 | div:only-child {width:100px} 34 | div:last-child {width:100px} 35 | div:nth-child {width:100px} 36 | div:nth-last-child {width:100px} 37 | div:empty {width:100px} 38 | div:target {width:100px} 39 | div:checked {width:100px} 40 | div::selection {width:100px} 41 | div:enabled {width:100px} 42 | div:disabled {width:100px} 43 | div:not(s) {width:100px} -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/FixStylePlugin.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import sublime, sublime_plugin 3 | import os 4 | from helper import getCkstylePath 5 | 6 | class FixstyleCommand(sublime_plugin.TextCommand): 7 | 8 | def run(self, edit): 9 | path = os.path.realpath(self.view.file_name().decode('utf-8')) 10 | if os.path.splitext(path)[1] != '.css': 11 | sublime.error_message('Not a CSS file!') 12 | return 13 | cmd = getCkstylePath() + ' fix -p "' + path + '"' 14 | returnValue = os.popen3(cmd) 15 | 16 | returnValue = returnValue[1].read() + returnValue[2].read() 17 | 18 | if returnValue.find('[console.error]') != -1: 19 | sublime.error_message(returnValue) 20 | else: 21 | region = sublime.Region(0, self.view.size()) 22 | msg = self.getRealMsg(returnValue) 23 | try: 24 | self.view.replace(edit, region, msg) 25 | except Exception: 26 | sublime.error_message('ERROR, maybe because file encoding charset is not utf-8'); 27 | self.view.end_edit(edit) 28 | 29 | def getRealMsg(self, msg): 30 | for charset in ['utf-8', 'gbk', 'gb2312']: 31 | try: 32 | msg.decode(charset) 33 | return msg.decode(charset) 34 | except Exception: 35 | pass 36 | return msg 37 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDHackRuleSetInCorrectWay.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDHackRuleSetInCorrectWay(ExtraChecker): 7 | 8 | '''{ 9 | "summary":"hack规则时的检查", 10 | "desc":"针对Firefox Opera Safari等浏览器的 hack 方式, 人人FED CSS编码规范中有详细的描述, 11 | 不允许使用规定之外的方式进行规则级别的hack" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'hack-ruleset' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg = 'not correct hacking way in "${selector}"' 18 | 19 | def check(self, ruleSet, config): 20 | if not ruleSet.nested: 21 | return True 22 | 23 | selector = ruleSet.selector.strip() 24 | if selector.find('@-moz-document') != -1: 25 | if selector != '@-moz-document url-prefix()': 26 | return False 27 | 28 | if selector.find('-webkit-min-device-pixel-ratio:0') != -1: 29 | if selector != '@media screen and (-webkit-min-device-pixel-ratio:0)' and selector.find('-webkit-min-device-pixel-ratio:10000') == -1: 30 | return False 31 | 32 | if selector.find('-webkit-min-device-pixel-ratio:10000') != -1: 33 | if selector.find('@media all') == -1 or selector.find('not all and') == -1 or selector.find('-webkit-min-device-pixel-ratio:0') == -1: 34 | return False 35 | 36 | return True 37 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/CssCompressPlugin.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import sublime, sublime_plugin 3 | import os 4 | from helper import getCkstylePath 5 | 6 | class CsscompressCommand(sublime_plugin.TextCommand): 7 | 8 | def run(self, edit): 9 | path = os.path.realpath(self.view.file_name()).decode('utf-8') 10 | if os.path.splitext(path)[1] != '.css': 11 | sublime.error_message('Not a CSS file!') 12 | return 13 | 14 | cmd = getCkstylePath() + ' compress -p "' + path + '"' 15 | returnValue = os.popen3(cmd) 16 | 17 | returnValue = returnValue[1].read() + returnValue[2].read() 18 | 19 | if returnValue.find('[console.error]') != -1: 20 | sublime.error_message(returnValue) 21 | else: 22 | region = sublime.Region(0, self.view.size()) 23 | msg = self.getRealMsg(returnValue) 24 | try: 25 | self.view.replace(edit, region, msg) 26 | except Exception: 27 | sublime.error_message('ERROR, maybe because file encoding charset is not utf-8'); 28 | self.view.end_edit(edit) 29 | 30 | def getRealMsg(self, msg): 31 | for charset in ['utf-8', 'gbk', 'gb2312']: 32 | try: 33 | msg.decode(charset) 34 | return msg.decode(charset) 35 | except Exception: 36 | pass 37 | return msg 38 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/FixStyleSafePlugin.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import sublime, sublime_plugin 3 | import os 4 | from helper import getCkstylePath 5 | 6 | class FixstylesafeCommand(sublime_plugin.TextCommand): 7 | 8 | def run(self, edit): 9 | path = os.path.realpath(self.view.file_name()).decode('utf-8') 10 | if os.path.splitext(path)[1] != '.css': 11 | sublime.error_message('Not a CSS file!') 12 | return 13 | 14 | cmd = getCkstylePath() + ' fix --safeMode -p "' + path + '"' 15 | returnValue = os.popen3(cmd) 16 | 17 | returnValue = returnValue[1].read() + returnValue[2].read() 18 | 19 | if returnValue.find('[console.error]') != -1: 20 | sublime.error_message(returnValue) 21 | else: 22 | region = sublime.Region(0, self.view.size()) 23 | msg = self.getRealMsg(returnValue) 24 | try: 25 | self.view.replace(edit, region, msg) 26 | except Exception: 27 | sublime.error_message('ERROR, maybe because file encoding charset is not utf-8'); 28 | self.view.end_edit(edit) 29 | 30 | def getRealMsg(self, msg): 31 | for charset in ['utf-8', 'gbk', 'gb2312']: 32 | try: 33 | msg.decode(charset) 34 | return msg.decode(charset) 35 | except Exception: 36 | pass 37 | return msg 38 | -------------------------------------------------------------------------------- /ckstyle/command/index.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | from .ConsoleCommandParser import handleCkStyleCmdArgs, handleFixStyleCmdArgs, handleCompressCmdArgs 4 | from .PluginManager import install, uninstall, installcmd, uninstallcmd, handleExtraCommand 5 | import sys 6 | from .usage import newUsage 7 | 8 | def ckstyle(): 9 | args = sys.argv 10 | if len(args) < 2: 11 | newUsage() 12 | return 13 | 14 | commands = { 15 | 'check' : handleCkStyleCmdArgs, 16 | 'fix': handleFixStyleCmdArgs, 17 | 'compress': handleCompressCmdArgs, 18 | 19 | 'install': install, 20 | 'add': install, 21 | 'get': install, 22 | 23 | 'uninstall': uninstall, 24 | 'remove': uninstall, 25 | 'rm': uninstall, 26 | 27 | 'installcmd': installcmd, 28 | 'addcmd': installcmd, 29 | 'getcmd': installcmd, 30 | 31 | 'rmcmd': uninstallcmd, 32 | 'removecmd': uninstallcmd, 33 | 'uninstallcmd': uninstallcmd 34 | } 35 | 36 | subcommand = args[1] 37 | if commands.has_key(subcommand): 38 | commands.get(subcommand)(args) 39 | else: 40 | handleExtraCommand(subcommand, args) 41 | 42 | #handleCkStyleCmdArgs() 43 | 44 | #def fixstyle(): 45 | # handleFixStyleCmdArgs() 46 | 47 | #def compress(): 48 | # handleCompressCmdArgs() 49 | 50 | #def main(): 51 | # ckstyle() 52 | 53 | if __name__ == '__main__': 54 | ckstyle() 55 | -------------------------------------------------------------------------------- /editor-plugins/fixstyle_for_sublime_text_2/FixStyleSingleLinePlugin.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import sublime, sublime_plugin 3 | import os 4 | from helper import getCkstylePath 5 | 6 | class FixstylesinglelineCommand(sublime_plugin.TextCommand): 7 | 8 | def run(self, edit): 9 | path = os.path.realpath(self.view.file_name()).decode('utf-8') 10 | if os.path.splitext(path)[1] != '.css': 11 | sublime.error_message('Not a CSS file!') 12 | return 13 | 14 | cmd = getCkstylePath() + ' fix -p --singleLine "' + path + '"' 15 | returnValue = os.popen3(cmd) 16 | 17 | returnValue = returnValue[1].read() + returnValue[2].read() 18 | 19 | if returnValue.find('[console.error]') != -1: 20 | sublime.error_message(returnValue) 21 | else: 22 | region = sublime.Region(0, self.view.size()) 23 | msg = self.getRealMsg(returnValue) 24 | try: 25 | self.view.replace(edit, region, msg) 26 | except Exception: 27 | sublime.error_message('ERROR, maybe because file encoding charset is not utf-8'); 28 | self.view.end_edit(edit) 29 | 30 | def getRealMsg(self, msg): 31 | for charset in ['utf-8', 'gbk', 'gb2312']: 32 | try: 33 | msg.decode(charset) 34 | return msg.decode(charset) 35 | except Exception: 36 | pass 37 | return msg 38 | -------------------------------------------------------------------------------- /tests/unit/check/FEDNoExpression.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: should not use expression in ".abcd" 6 | 0: should not use expression in ".abcd:hover" 7 | 0: should not use expression in ".abcdef:hover" 8 | 0: should not use expression in ".expression-without-this-equals" 9 | 0: should add hack for expression in ".expression-with-this-equals" 10 | 11 | 1: ".abcd, .abcd:hover, .abcdef:hover" contains the same rules in "FEDNoExpression.css" 12 | 1: ".expression-with-expressions, * html .with-star-html-do-not-need-hack" contains the same rules in "FEDNoExpression.css" 13 | } 14 | 15 | .abcd { 16 | width: expression(function() {fdasfdas}); 17 | } 18 | 19 | .abcd:hover { 20 | *width: expression(function() {fdasfdas}); 21 | } 22 | 23 | .abcdef:hover { 24 | _width: expression(function() {fdasfdas}); 25 | } 26 | 27 | .expression-without-this-equals { 28 | width: expression(function() {this = 11;}); 29 | } 30 | 31 | .expression-with-this-equals { 32 | width: expression(function() {this.style.width = 11;}); 33 | } 34 | 35 | .expression-with-expressions { 36 | *width: expression(Expressions.style.width(this,this.offsetWidth-32)); 37 | } 38 | 39 | .expression-with-runtime-style { 40 | *width: expression(function() {this.runtimeStyle.width = 11;}); 41 | } 42 | 43 | * html .with-star-html-do-not-need-hack { 44 | width: expression(Expressions.style.width(this,this.offsetWidth-32)); 45 | } -------------------------------------------------------------------------------- /ckstyle/entity/ExtraStatement.py: -------------------------------------------------------------------------------- 1 | from .EntityUtil import Cleaner, ALL 2 | from .RuleSet import RuleSet 3 | 4 | class ExtraStatement(RuleSet): 5 | def __init__(self, operator, statement, comment, styleSheet = None): 6 | self.extra = True 7 | self.nested = False 8 | self.selector = self.operator = operator.strip() 9 | self.comment = comment 10 | self.statement = statement 11 | self.styleSheet = styleSheet 12 | 13 | self.fixedSelector = '' 14 | self.fixedStatement = '' 15 | 16 | self.browser = ALL 17 | self.toBeUsed = {} 18 | 19 | def isImport(self): 20 | return self.operator == '@import' 21 | 22 | def rebase(self): 23 | self.fixedSelector = '' 24 | self.fixedStatement = '' 25 | 26 | def isOpmOperator(self): 27 | return self.operator.find('@-css-compiler') != -1 28 | 29 | def compress(self, browser = ALL): 30 | # do not export @-css-compiler to online 31 | if self.isOpmOperator(): 32 | return '' 33 | 34 | if not self.browser & browser: 35 | return '' 36 | msg = Cleaner.clean(self.statement) 37 | if not msg.endswith('}') and not msg.endswith(';'): 38 | msg = msg + ';' 39 | return msg 40 | 41 | def fixed(self, config): 42 | return self.statement.strip() if len(self.comment) == 0 else self.comment + '\n' + self.statement.strip() 43 | 44 | def __str__(self): 45 | return '%s' % self.statement 46 | -------------------------------------------------------------------------------- /ckstyle/command/args.py: -------------------------------------------------------------------------------- 1 | class CommandArgs(): 2 | def __init__(self): 3 | self.operation = None 4 | self.errorLevel = 2 5 | self.recursive = False 6 | self.printFlag = False 7 | self.extension = '.ckstyle.txt' 8 | self.include = 'all' 9 | self.exclude = 'none' 10 | self.standard = '' 11 | self.exportJson = False 12 | self.ignoreRuleSets = ['@unit-test-expecteds'] 13 | self.fixedExtension = '.fixed.css' 14 | self.fixToSingleLine = False 15 | self.compressConfig = CompressArgs() 16 | self.safeMode = False 17 | self.noBak = False 18 | 19 | # current browser 20 | self._curBrowser = None 21 | 22 | # plugin config for developers, add plugin section in ckstyle.ini 23 | # 24 | # [plugin] 25 | # pluginA = 1 26 | self.pluginConfig = {} 27 | 28 | def __str__(self): 29 | return 'errorLevel: %s\n recursive: %s\n printFlag: %s\n extension: %s\n include: %s\n exclude: %s' % (self.errorLevel, self.recursive, self.printFlag, self.extension, self.include, self.exclude) 30 | 31 | class CompressArgs(): 32 | def __init__(self): 33 | self.extension = '.min.css' 34 | self.combineFile = True 35 | self.browsers = None 36 | self.noBak = False 37 | 38 | def __str__(self): 39 | return 'extension: %s, combineFile: %s, browsers: %s' % (self.recursive, self.extension, self.combineAttr, self.combineRuleSet, self.combineFile, self.browsers) 40 | -------------------------------------------------------------------------------- /tests/unit/compress/CompressWithOrder.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _basic() 5 | _with_hack() 6 | _with_extra() 7 | _with_different_level() 8 | _with_css3_props() 9 | 10 | def _basic(): 11 | msg = doCssCompress('/* @wangjeaf */ .test {width: 100px; display:none; height: 100px;}') 12 | equal(msg, '.test{display:none;width:100px;height:100px}', 'general order compress is ok') 13 | 14 | def _with_hack(): 15 | msg = doCssCompress('.test {_width: 100px; *display:none;_display:none;display:block\9;*height: 100px;}') 16 | equal(msg, '.test{*display:none;_display:none;display:block\9;_width:100px;*height:100px}', 'hack order compress is ok') 17 | 18 | def _with_extra(): 19 | msg = doCssCompress('@import (url-here);.test {_width: 100px; *height: 100px;}') 20 | equal(msg, '@import (url-here);.test{_width:100px;*height:100px}', 'extra order compress is ok') 21 | 22 | def _with_different_level(): 23 | msg = doCssCompress('.test {cursor:pointer;color:red;line-height:10px;background-color:red;margin-top:10px;padding-bottom:10px;position:relative;}') 24 | equal(msg, '.test{position:relative;margin-top:10px;padding-bottom:10px;background-color:red;line-height:10px;color:red;cursor:pointer}', 'different levels is ok') 25 | 26 | def _with_css3_props(): 27 | msg = doCssCompress('.test {-moz-transform:xxxx;-webkit-transform:xxxx;transform:xxxx;*zoom:1;width:100px;display:none;}') 28 | equal(msg, '.test{display:none;width:100px;*zoom:1;-webkit-transform:xxxx;-moz-transform:xxxx;transform:xxxx}', 'different levels is ok') 29 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoExpression.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDNoExpression(RuleChecker): 7 | 8 | '''{ 9 | "summary":"不要使用非一次性表达式", 10 | "desc":"IE下,非一次性expression对性能有很大的影响,或许一次鼠标移动, 11 | 将触发成千上万次的expression表达式的执行, 12 | 因此,为了浏览器的更新换代,应该杜绝使用非一次性表达式。
13 | 本工具针对一次性表达式的检查,将判断expression中是否有如下两个内容:
14 | 1. Expressions
15 | 2. this.style.attrName = " 16 | }''' 17 | 18 | def __init__(self): 19 | self.id = 'no-expression' 20 | self.errorLevel = ERROR_LEVEL.ERROR 21 | self.errorMsg_use = 'should not use expression in "${selector}" ' 22 | self.errorMsg_hack = 'should add hack for expression in "${selector}"' 23 | self.errorMsg = '' 24 | 25 | def check(self, rule, config): 26 | value = rule.value 27 | name = rule.name 28 | replaced = value.replace(' ', '') 29 | 30 | if value.find('expression') == -1: 31 | return True 32 | 33 | if replaced.find('Expressions') != -1 or replaced.find('this.style.' + name + '=') != -1 or replaced.find('this.runtimeStyle.' + name + '=') != -1: 34 | if rule.name == rule.strippedName: 35 | selector = rule.selector.replace(' ', '') 36 | if selector.find('*html') == -1: 37 | self.errorMsg = self.errorMsg_hack 38 | return False 39 | return True 40 | 41 | self.errorMsg = self.errorMsg_use 42 | return False 43 | -------------------------------------------------------------------------------- /tests/unit/fix/FixFEDNoUnitAfterZero.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _rgba() 5 | _rgba_no_space() 6 | 7 | def _rgba_no_space(): 8 | css = '''.test1 { 9 | box-shadow: inset 0 0px 0 0 rgba(0,0px,0px,0.1); 10 | }''' 11 | 12 | fixer, msg = doFix(css, '') 13 | expectedFixed = '''.test1 { 14 | box-shadow: inset 0 0 0 0 rgba(0,0,0,.1); 15 | }''' 16 | equal(msg, expectedFixed, 'rgba no space is also ok') 17 | 18 | 19 | def _rgba(): 20 | css = '''html { 21 | -webkit-tap-highlight-color: rgba(0px, 0px, 0px, 0.1); 22 | }''' 23 | 24 | fixer, msg = doFix(css, '') 25 | 26 | styleSheet = fixer.getStyleSheet() 27 | ruleSet = styleSheet.getRuleSets()[0] 28 | color = ruleSet.getRuleByName('tap-highlight-color') 29 | equal(color.fixedValue, 'rgba(0, 0, 0, .1)', 'tap-highlight-color is fixed') 30 | equal(color.value, 'rgba(0px, 0px, 0px, 0.1)', 'tap-highlight-color is ok') 31 | 32 | css = '''.current-hot-films-ul { 33 | -webkit-transition:all 0.5s ease-in-out 0s; 34 | -moz-transition:all 0.5s ease-in-out 0s; 35 | -o-transition:all 0.5s ease-in-out 0s; 36 | transition:all 0.5s ease-in-out 0s; 37 | }''' 38 | expectedFixed = '''.current-hot-films-ul { 39 | -webkit-transition: all .5s ease-in-out 0s; 40 | -moz-transition: all .5s ease-in-out 0s; 41 | -o-transition: all .5s ease-in-out 0s; 42 | transition: all .5s ease-in-out 0s; 43 | }''' 44 | 45 | fixer, msg = doFix(css, '') 46 | equal(msg.strip(), expectedFixed.strip(), 'transition is ok, 0s can not be shorter') 47 | -------------------------------------------------------------------------------- /tests/unit/fix/SoManyColors.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | css = '''.ui-dialog .ui-dialog-content .btn.confirm { 5 | background-color: #2996e3; 6 | background-image: -moz-linear-gradient(top, #2692de, #2d9deb); 7 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#2692de), to(#2d9deb)); 8 | background-image: -webkit-linear-gradient(top, #2692de, #2d9deb); 9 | background-image: -o-linear-gradient(top, #2692de, #2d9deb); 10 | background-image: linear-gradient(to bottom, #2692de, #2d9deb); 11 | background-repeat: repeat-x; 12 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2692', endColorstr='#ff2d9d', GradientType=0); 13 | color: #ffffff; 14 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); 15 | border-color: #2D9DEB; 16 | }''' 17 | 18 | expected = '''.ui-dialog .ui-dialog-content .btn.confirm { 19 | border-color: #2D9DEB; 20 | background-color: #2996E3; 21 | background-image: -moz-linear-gradient(top,#2692DE,#2D9DEB); 22 | background-image: -webkit-gradient(linear,0 0,0 100%,from(#2692DE),to(#2D9DEB)); 23 | background-image: -webkit-linear-gradient(top,#2692DE,#2D9DEB); 24 | background-image: -o-linear-gradient(top,#2692DE,#2D9DEB); 25 | background-image: linear-gradient(to bottom,#2692DE,#2D9DEB); 26 | background-repeat: repeat-x; 27 | color: #FFF; 28 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF2692',endColorstr='#FF2D9D',GradientType=0); 29 | text-shadow: 0 1px 1px rgba(255,255,255,.75); 30 | }''' 31 | fixer, msg = doFix(css, '') 32 | equal(msg, expected, 'so many colors are ok'); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) individual contributors of this project. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of CSSCheckStyle/ckstyle nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDSingleLineSpaces.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDSingleLineSpaces(RuleChecker): 7 | 8 | '''{ 9 | "summary":"单行的空格检查", 10 | "desc":"单行CSS编码风格相关的空格检查,具体内容请参见CSS编码规范" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'single-line-space' 15 | self.errorLevel = ERROR_LEVEL.LOG 16 | self.errorMsg_noSpace = 'should have one "space" before "${name}" in "${selector}"' 17 | self.errorMsg_spaceEnd = 'should not have "space" after "${name}" in "${selector}"' 18 | self.errorMsg_noSpaceBeforeValue = 'should have one "space" before value of "${name}" in "${selector}"' 19 | self.errorMsg_extraSpaceAfterValue = 'found extra "space" after value of "${name}" in "${selector}"' 20 | self.errorMsg = '' 21 | 22 | def check(self, rule, config): 23 | singleLine = rule.getRuleSet().getSingleLineFlag() 24 | if not singleLine: 25 | return True 26 | 27 | if not rule.roughName.startswith(' '): 28 | self.errorMsg = self.errorMsg_noSpace 29 | return False 30 | 31 | if rule.roughName.endswith(' '): 32 | self.errorMsg = self.errorMsg_spaceEnd 33 | return False 34 | 35 | if not rule.roughValue.startswith(' '): 36 | self.errorMsg = self.errorMsg_noSpaceBeforeValue 37 | return False 38 | 39 | value = rule.roughValue.strip() 40 | if value.endswith(' ;') or value.endswith(' '): 41 | self.errorMsg = self.errorMsg_extraSpaceAfterValue 42 | return False 43 | 44 | return True 45 | -------------------------------------------------------------------------------- /tests/unit/check/FEDMultiLineSpaces.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: should have 4 spaces before "width" in ".no-space" 6 | 2: should have 4 spaces before "width" in ".one-space" 7 | 2: should have 4 spaces before "width" in ".two-spaces" 8 | 2: should have 4 spaces before "width" in ".three-spaces" 9 | 2: should have 4 spaces before "width" in ".five-spaces" 10 | 2: should have 4 spaces before "width" in ".five-more-spaces" 11 | 2: should have 4 spaces before "nav-down" in ".css-three-attrs-no-prefix-extra-space" 12 | 2: should have (only) one "space" before value of "width" in ".test-space-before-value" 13 | 14 | 1: ".test-space-before-value, .no-space, .one-space, .two-spaces, .three-spaces, .four-spaces, .five-spaces, .five-more-spaces" contains the same rules in "FEDMultiLineSpaces.css" 15 | 1: ".css-three-attrs-no-prefix-extra-space, .css-three-attrs-no-prefix-no-extra" contains the same rules in "FEDMultiLineSpaces.css" 16 | } 17 | 18 | .test-space-before-value { 19 | width:100px; 20 | } 21 | 22 | .css-three-attrs-no-prefix-extra-space { 23 | nav-down: 1; 24 | } 25 | 26 | .css-three-attrs-no-prefix-no-extra { 27 | nav-down: 1; 28 | } 29 | 30 | .no-space { 31 | width: 100px; 32 | } 33 | 34 | .one-space { 35 | width: 100px; 36 | } 37 | 38 | .two-spaces { 39 | width: 100px; 40 | } 41 | 42 | .three-spaces { 43 | width: 100px; 44 | } 45 | 46 | .four-spaces { 47 | width: 100px; 48 | } 49 | 50 | .five-spaces { 51 | width: 100px; 52 | } 53 | 54 | .five-more-spaces { 55 | width: 100px; 56 | } 57 | 58 | .single-line { width: 100px; height: 200px; } 59 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDSingleLineBraces.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDSingleLineBraces(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"单行的括号检查", 10 | "desc":"与单行CSS编码风格相关的括号检查" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'single-line-brace' 15 | self.errorLevel = ERROR_LEVEL.LOG 16 | self.errorMsg_openingBrace = 'should have "only one space" before the opening brace in "${selector}"' 17 | self.errorMsg_openingBraceEnd = 'should have "only one space" after the opening brace in "${selector}"' 18 | self.errorMsg_closingBrace = 'should have "only one space" before the closing brace in "${selector}"' 19 | self.errorMsg_closingBraceEnd = 'should have "only one space" before the closing brace in "${selector}"' 20 | self.errorMsg = '' 21 | 22 | def check(self, ruleSet, config): 23 | singleLine = ruleSet.getSingleLineFlag() 24 | if not singleLine: 25 | return True 26 | selector = ruleSet.roughSelector 27 | if selector.find(',') == -1: 28 | if selector.endswith(' ') or not selector.endswith(' '): 29 | self.errorMsg = self.errorMsg_openingBrace 30 | return False 31 | else: 32 | return True 33 | 34 | value = ruleSet.roughValue 35 | if not value.startswith(' ') or value.startswith(' '): 36 | self.errorMsg = self.errorMsg_openingBraceEnd 37 | return False 38 | if not value.endswith(' ') or value.endswith(' '): 39 | self.errorMsg = self.errorMsg_closingBraceEnd 40 | return False 41 | return True 42 | -------------------------------------------------------------------------------- /ckstyle/command/PluginManager.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | import sys 5 | from ckstyle.cmdconsole.ConsoleClass import console 6 | from .helper import fetchPlugin, fetchCmdPlugin, removePlugin, removeCmdPlugin, findCmdPlugin 7 | from .usage import newUsage 8 | 9 | def getNameAndVersion(args): 10 | argLen = len(args) 11 | if argLen < 3 or argLen > 4: 12 | console.error('wrong arg length, use "ckstyle -h" to see help.') 13 | return None, None 14 | pluginName = args[2] 15 | version = '' 16 | if argLen == 4: 17 | version = args[3] 18 | 19 | return pluginName, version 20 | 21 | def install(args): 22 | pluginName, version = getNameAndVersion(args) 23 | if pluginName is None: 24 | return 25 | fetchPlugin(pluginName, version) 26 | 27 | def uninstall(args): 28 | pluginName, version = getNameAndVersion(args) 29 | if pluginName is None: 30 | return 31 | removePlugin(pluginName, version) 32 | 33 | def installcmd(args): 34 | pluginName, version = getNameAndVersion(args) 35 | if pluginName is None: 36 | return 37 | fetchCmdPlugin(pluginName, version) 38 | 39 | def uninstallcmd(args): 40 | pluginName, version = getNameAndVersion(args) 41 | if pluginName is None: 42 | return 43 | removeCmdPlugin(pluginName, version) 44 | 45 | def handleExtraCommand(command, args): 46 | if command.startswith('-') or command.startswith('.'): 47 | newUsage() 48 | return 49 | if not findCmdPlugin(command): 50 | console.show('[CKstyle ERROR] CKstyle can not find the subcommand: "%s".' % command) 51 | console.show('[CKstyle ERROR] Maybe you can type "ckstyle installcmd %s" to install this command from ckstyle-pm.' % command) 52 | return 53 | cmd = fetchCmdPlugin(command) 54 | if cmd is None: 55 | return 56 | cmd() 57 | -------------------------------------------------------------------------------- /tests/unit/order/InAMessOrder.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | 4 | def doTest(): 5 | fixer, msg = doFix("a.feed-back-v6 { display: block; position: fixed; _position: absolute; top: 155px; _top: expression(documentElement.scrollTop + 'px'); _margin-top: 155px; right: 0; padding: 10px; font-size: 14px; font-weight: bold; width: 1em; background: #F7F7FF; z-index: 1999; }", '') 6 | 7 | equal(fixer.doCompress(), "a.feed-back-v6{display:block;position:fixed;_position:absolute;top:155px;_top:expression(documentElement.scrollTop + 'px');right:0;width:1em;_margin-top:155px;padding:10px;background:#F7F7FF;font-size:14px;font-weight:bold;z-index:1999}", 'compress feed-back-v6 is ok') 8 | 9 | styleSheet = fixer.getStyleSheet() 10 | equal(len(styleSheet.getRuleSets()), 1, 'one ruleset') 11 | ruleSet = styleSheet.getRuleSets()[0] 12 | equal(ruleSet.selector, 'a.feed-back-v6', 'yes it is a.feed-back-v6') 13 | 14 | rules = ruleSet.getRules() 15 | equal(rules[0].name, 'display', 'element name is ok') 16 | equal(rules[1].name, 'position', 'element name is ok') 17 | equal(rules[2].name, 'position', 'element name is ok') 18 | equal(rules[3].name, 'top', 'element name is ok') 19 | equal(rules[4].name, 'top', 'element name is ok') 20 | equal(rules[5].name, 'right', 'element name is ok') 21 | equal(rules[6].name, 'width', 'element name is ok') 22 | equal(rules[7].name, 'margin-top', 'element name is ok') 23 | equal(rules[8].name, 'padding', 'element name is ok') 24 | equal(rules[9].name, 'background', 'element name is ok') 25 | equal(rules[10].name, 'font-size', 'element name is ok') 26 | equal(rules[11].name, 'font-weight', 'element name is ok') 27 | equal(rules[12].name, 'z-index', 'element name is ok') 28 | -------------------------------------------------------------------------------- /tests/unit/compress/CompressFile.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _basic() 5 | _one_line_file() 6 | _with_extra() 7 | _compress_with_hack_chars() 8 | _extra_statement() 9 | _expression() 10 | 11 | def _basic(): 12 | msg = doCssFileCompress('_file.css') 13 | equal(msg, ".test{width:100px;height:200px;_z-index:111}@keyframes 'name'{10%{width:100px}}.another{*width:100px;background-color:#ABC;color:#DDD}", 'file compressed') 14 | 15 | def _one_line_file(): 16 | msg = doCssFileCompress('_one_line_file.css') 17 | equal(msg, ".test{width:100px;height:200px;_z-index:111}@keyframes 'name'{10%{width:100px}}.another{*width:100px;background-color:#ABC;color:#DDD}", 'file compressed') 18 | 19 | def _with_extra(): 20 | msg = doCssFileCompress('_with_extra.css') 21 | equal(msg, "@charset utf-8;@import url('xxxxx');@namespace lalala;.test{width:100px;height:200px;_z-index:111}@import url('xxx2');@import url('xxx3');", 'file compressed') 22 | 23 | def _compress_with_hack_chars(): 24 | msg = doCssFileCompress('_compress_special_hack_chars.css') 25 | equal(msg, "li:nth-child(even){background:gray}* html li.even{background:gray}.test[^=aaa]{background:gray}.test1,.test2{width:100px}", 'file compressed') 26 | 27 | def _extra_statement(): 28 | msg = doCssFileCompress('_extra_statement.css') 29 | # do not show @-css-compile content after compress 30 | equal(msg, "@charset utf-8;@import url(fdafdas/fdafdas.css);", 'extra statement compressed') 31 | 32 | def _expression(): 33 | msg = doCssFileCompress('_expression.css') 34 | equal(msg, "*html .feed-comment textarea{behavior:expression(function(ele){ele.runtimeStyle.behavior='none';Expressions.pseudo.hover(ele,'textarea_hover')}(this))}", 'expression compressed') 35 | -------------------------------------------------------------------------------- /tests/unit/hacks/HackRules.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _ie() 5 | _other() 6 | #_ms() 7 | #_moz() 8 | #_o() 9 | 10 | def _ie(): 11 | equal(doRuleDetect('_width', 'fd'), IE6, '_width') 12 | equal(doRuleDetect('*width', 'fd'), IE6 | IE7, '*width') 13 | equal(doRuleDetect('+width', 'fd'), IE6 | IE7, '+width') 14 | equal(doRuleDetect('width', r'fd\9'), ALLIE, 'width\\9') 15 | equal(doRuleDetect('width', r'fd\0/'), IE8, 'width\\0/') 16 | equal(doRuleDetect('width', r'fd\0'), IE8 | IE9PLUS, 'width\\0') 17 | equal(doRuleDetect('zoom', '1'), ALLIE, 'zoom') 18 | equal(doRuleDetect('behavior', '1'), ALLIE, 'behavior') 19 | equal(doRuleDetect('filter', '1'), ALLIE, 'filter') 20 | equal(doRuleDetect('width', 'xxxx.Microsoft.AlphaImageLoader'), ALLIE, 'Microsoft') 21 | equal(doRuleDetect('width', 'xxxx.microsoft.AlphaImageLoader'), ALLIE, 'microsoft') 22 | equal(doRuleDetect('width', 'expression()'), ALLIE, 'expression') 23 | 24 | def _other(): 25 | equal(doRuleDetect('-webkit-transition', 'fd'), WEBKIT, '-webkit-transition') 26 | equal(doRuleDetect('-moz-transition', 'fd'), FIREFOX, '-moz-transition') 27 | equal(doRuleDetect('-ms-transition', 'fd'), IE9PLUS, '-ms-transition') 28 | equal(doRuleDetect('-khtml-transition', 'fd'), ALLIE, '-khtml-transition') 29 | equal(doRuleDetect('-o-transition', 'fd'), OPERA, '-o-transition') 30 | 31 | equal(doRuleDetect('background', '-webkit-linear-gradient()'), WEBKIT, '-webkit-gradient') 32 | equal(doRuleDetect('background', '-moz-linear-gradient()'), FIREFOX, '-moz-gradient') 33 | equal(doRuleDetect('background', '-ms-linear-gradient()'), IE9PLUS, '-ms-gradient') 34 | equal(doRuleDetect('background', '-khtml-linear-gradient()'), ALLIE, '-khtml-gradient') 35 | equal(doRuleDetect('background', '-o-linear-gradient()'), OPERA, '-o-gradient') -------------------------------------------------------------------------------- /tests/unit/check/FEDMultiLineSelectors.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: should enter in multi-selector, in ".multia, .multiaa" 6 | 2: too many "enter"s in ".multiaaa, .multiaaaa" 7 | 2: extra "space" before comma in ".multib , .multic" 8 | 2: extra "space" after comma in ".multid, .multie" 9 | 2: selector should not start with "space" in ".multif, .multig" 10 | 2: selector should not start with "space" in ".multip, .multiq" 11 | 2: comma should at the end of selector in ".multil , .multim" 12 | 2: selector should end with only one space ".multih, .multii" 13 | 2: selector should end with only one space ".multin, .multio" 14 | 2: selector should end with only one space ".multij, .multik" 15 | 16 | 1: ".multia, .multiaa, .multiaaa, .multiaaaa, .multi-selector, .multi-selectorb, .multib , .multic, .multid, .multie, .multif, .multig, .multih, .multii, .multij, .multik, .multil , .multim, .multip, .multiq" contains the same rules in "FEDMultiLineSelectors.css" 17 | } 18 | 19 | .multia, .multiaa { width: 100px; height: 100px; } 20 | 21 | .multiaaa, 22 | 23 | .multiaaaa { width: 100px; height: 100px; } 24 | 25 | .multi-selector, 26 | .multi-selectorb { width: 100px; height: 100px; } 27 | 28 | .multib , 29 | .multic { width: 100px; height: 100px; } 30 | 31 | .multid, 32 | .multie { width: 100px; height: 100px; } 33 | 34 | .multif, 35 | .multig { width: 100px; height: 100px; } 36 | 37 | .multih, 38 | .multii{ width: 100px; height: 100px; } 39 | 40 | .multij, 41 | .multik { width: 100px; height: 100px; } 42 | 43 | .multil 44 | , .multim { width: 100px; height: 100px; } 45 | 46 | .multin, 47 | .multio 48 | { 49 | width: 100px; 50 | } 51 | 52 | .multip, 53 | .multiq { width: 100px; height: 100px; } 54 | 55 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDMultiLineBraces.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDMultiLineBraces(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"多行CSS风格的括号检查", 10 | "desc":"用于检查多行风格下的 {} 的编写风格,前后空格符和回车符的情况等。" 11 | }''' 12 | 13 | def __init__(self): 14 | self.id = 'multi-line-brace' 15 | self.errorLevel = ERROR_LEVEL.LOG 16 | self.errorMsg_shouldEnterAfterOpenningBrace = 'should "enter" after the opening brace in "${selector}"' 17 | self.errorMsg_shouldEnterBeforeClosingBrace = 'should "enter" before the closing brace in "${selector}"' 18 | self.errorMsg_extraSpaceAfterOpeningBrace = 'extra "space" after the opening brace in "${selector}"' 19 | self.errorMsg_everyAttrShouldInSingleLine = 'every name/value should in single line in "${selector}"' 20 | self.errorMsg = '' 21 | 22 | def check(self, ruleSet, config): 23 | singleLine = ruleSet.getSingleLineFlag() 24 | if singleLine: 25 | return True 26 | 27 | value = ruleSet.roughValue 28 | splited = value.split('\n') 29 | if splited[0].strip() != '': 30 | self.errorMsg = self.errorMsg_shouldEnterAfterOpenningBrace 31 | return False 32 | 33 | if splited[0].strip() == '' and splited[0].startswith(' '): 34 | self.errorMsg = self.errorMsg_extraSpaceAfterOpeningBrace 35 | return False 36 | 37 | ruleLength = len(ruleSet.getRules()) 38 | if ruleLength != 0 and len(value.strip().split('\n')) != ruleLength: 39 | self.errorMsg = self.errorMsg_everyAttrShouldInSingleLine 40 | return False 41 | 42 | if not value.replace(' ', '').endswith('\n'): 43 | self.errorMsg = self.errorMsg_shouldEnterBeforeClosingBrace 44 | return False 45 | 46 | return True 47 | -------------------------------------------------------------------------------- /tests/unit/fix/FEDCombineSameRuleSets.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _combine_should_not_make_mistake() 5 | _totally_same_ruleset() 6 | do_not_touch_background_position() 7 | _do_not_change_comment() 8 | 9 | def _combine_should_not_make_mistake(): 10 | css = '''.a {width:0px} 11 | .a, .b{width:1px} 12 | .b{width:0px}''' 13 | 14 | expected = '''.a { 15 | width: 0; 16 | } 17 | 18 | .a, 19 | .b { 20 | width: 1px; 21 | } 22 | 23 | .b { 24 | width: 0; 25 | }''' 26 | fixer, msg = doFix(css, '') 27 | equal(msg, expected, 'do not make mistake when combine rulesets'); 28 | 29 | def do_not_touch_background_position(): 30 | css = '''.a {background-position: 0 0} 31 | .test {width:1} 32 | .b {background-position: 0 0} ''' 33 | 34 | expected = '''.a { 35 | background-position: 0 0; 36 | } 37 | 38 | .test { 39 | width: 1; 40 | } 41 | 42 | .b { 43 | background-position: 0 0; 44 | }''' 45 | fixer, msg = doFix(css, '') 46 | equal(msg, expected, 'do not combine background-position'); 47 | 48 | def _totally_same_ruleset(): 49 | css = '''/*fdafda*/ 50 | .page-title { 51 | width: 100px; 52 | padding: 0px 1px; 53 | } 54 | 55 | .page-title { 56 | width: 100px; 57 | padding: 0px 1px; 58 | }''' 59 | 60 | expected = '''/* fdafda */ 61 | .page-title { 62 | width: 100px; 63 | padding: 0 1px; 64 | }''' 65 | fixer, msg = doFix(css, '') 66 | equal(msg, expected, 'it is the same ruleset'); 67 | 68 | def _do_not_change_comment(): 69 | css = '''/*fdafda, fda,fda,fdas 70 | */ 71 | .page-title { 72 | width: 100px; 73 | padding: 0px 1px; 74 | } 75 | 76 | .page-title { 77 | width: 100px; 78 | padding: 0px 1px; 79 | }''' 80 | 81 | expected = '''/*fdafda, fda,fda,fdas 82 | */ 83 | .page-title { 84 | width: 100px; 85 | padding: 0 1px; 86 | }''' 87 | fixer, msg = doFix(css, '') 88 | equal(msg, expected, 'do not change comment is ok'); -------------------------------------------------------------------------------- /ckstyle/entity/Rule.py: -------------------------------------------------------------------------------- 1 | from .EntityUtil import Cleaner, ALL 2 | 3 | class Rule(): 4 | def __init__(self, selector, name, value, ruleSet): 5 | self.roughName = name 6 | self.roughValue = value 7 | self.roughSelector = selector 8 | 9 | self.name = Cleaner.clearName(name) 10 | self.value = Cleaner.clearValue(value) 11 | self.selector = Cleaner.clearSelector(selector) 12 | 13 | self.strippedName = name.strip() 14 | self.strippedValue = value.strip() 15 | self.strippedSelector = selector.strip() 16 | 17 | self.fixedName = '' 18 | self.fixedValue = '' 19 | 20 | self.ruleSet = ruleSet 21 | 22 | self.browser = ALL 23 | self.toBeUsed = {} 24 | 25 | def rebase(self): 26 | self.fixedName = '' 27 | self.fixedValue = '' 28 | 29 | def reset(self, name, value): 30 | self.roughName = self.name = self.strippedName = self.fixedName = name 31 | self.roughValue = self.value = self.strippedValue = self.fixedValue = value 32 | 33 | def compress(self, browser = ALL): 34 | #print self.strippedName, self.value, bin(self.browser), bin(browser), self.browser & browser 35 | if not self.browser & browser: 36 | return '' 37 | name = self.name if self.fixedName == '' else self.fixedName.strip() 38 | value = self.value if self.fixedValue == '' else self.fixedValue.strip() 39 | return name + ':' + Cleaner.clean(value) + ';' 40 | 41 | def fixed(self): 42 | name = self.strippedName if self.fixedName == '' else self.fixedName 43 | value = self.strippedValue if self.fixedValue == '' else self.fixedValue 44 | return name + ': ' + Cleaner.clean(value) + ';' 45 | 46 | def getRuleSet(self): 47 | return self.ruleSet 48 | 49 | def __str__(self): 50 | return ' roughName: %s\n name: %s\n roughValue: %s\n value: %s\n' % (self.roughName, self.name, self.roughValue, self.value) 51 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDUseLowerCaseProp.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDUseLowerCaseProp(RuleChecker): 7 | 8 | '''{ 9 | "summary":"属性名称应该用小写", 10 | "desc":"所有的CSS属性名称一律小写,例如 width ,大写的方式是不正确的, 11 | 例如: WIDTH:100px;" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'lowercase-prop' 16 | self.errorLevel = ERROR_LEVEL.WARNING 17 | self.errorMsg_name = '"${name}" should use lower case, in "${selector}"' 18 | self.errorMsg_value = 'value of "${name}" should use lower case, in "${selector}"' 19 | self.errorMsg = '' 20 | 21 | def check(self, rule, config): 22 | value = rule.value 23 | name = rule.strippedName 24 | 25 | # give it to FED16ColorShouldUpper.py 26 | if name == 'color': 27 | return True 28 | 29 | if value.find('expression') != -1: 30 | return True 31 | 32 | if name.lower() != name: 33 | self.errorMsg = self.errorMsg_name 34 | return False 35 | 36 | if value.find('#') != -1: 37 | return True 38 | 39 | if name != 'font' and name != 'font-family' and value != value.lower() and value.find('#') == -1: 40 | self.errorMsg = self.errorMsg_value 41 | return False 42 | 43 | if name == 'font-family': 44 | return True 45 | 46 | if name == 'font': 47 | if value.find(',') != -1: 48 | # font: italic bold 12px/30px 'Courier New', Georgia, serif; 49 | other = ' '.join(value.split(',')[0].split("'")[0].split(' ')[0:-1]) 50 | if other != other.lower(): 51 | self.errorMsg = self.errorMsg_value 52 | return False 53 | return True 54 | 55 | if value.lower() != value: 56 | self.errorMsg = self.errorMsg_value 57 | return False 58 | 59 | return True 60 | -------------------------------------------------------------------------------- /tests/unit/check/FEDStyleShouldInOrder.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | 5 | /** 6 | correct order: 7 | 0 : ['display', 'list-style', 'position', 'float', 'clear'], 8 | 2 : ['width', 'height', 'margin', 'padding', 'border'], 9 | 4 : ['background'], 10 | 6 : ['line-height'], 11 | 8 : ['color', 'font', 'text-decoration', 'text-align', 'text-indent', 'vertical-align', 'white-space', 'content'], 12 | 10: ['cursor', 'z-index', 'zoom'], 13 | 12: ['transform', 'transition', 'animation', 'box-shadow', 'border-radius'] 14 | # 14 : ['other'] 15 | */ 16 | 17 | @unit-test-expecteds { 18 | 1: "width" should after "display" in ".test-width-display" (order: display/box/text/other/css3) 19 | 1: "background-color" should after "list-style" in ".test-list-style-background" (order: display/box/text/other/css3) 20 | 1: "z-index" should after "height" in ".test-other" (order: display/box/text/other/css3) 21 | 0: unknown attribute name "nav" found in ".test-unknown" 22 | 1: "nav" should after "z-index" in ".test-unknown" (order: display/box/text/other/css3) 23 | 1: "width" should after "_display" in ".test-with-hack" (order: display/box/text/other/css3) 24 | } 25 | 26 | .test-width-display { 27 | width: 100px; 28 | display: none; 29 | } 30 | 31 | .test-list-style-background { 32 | background-color: red; 33 | list-style: none; 34 | } 35 | 36 | .test-same-order-box { 37 | width: 100px; 38 | height: 100px; 39 | margin: 0; 40 | padding: 0; 41 | border: none; 42 | } 43 | 44 | .test-same-order-display { 45 | display: none; 46 | position: relative; 47 | float: left; 48 | list-style: none; 49 | clear: both; 50 | } 51 | 52 | .test-other { 53 | width: 100px; 54 | z-index: 100; 55 | height: 100px; 56 | } 57 | 58 | .test-unknown { 59 | nav: false; 60 | z-index: 100; 61 | } 62 | 63 | .test-with-hack { 64 | width: 100px; 65 | _display: none; 66 | *display: inline-block; 67 | *zoom: 1; 68 | } 69 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDRemoveDuplicatedAttr.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | 6 | class FEDRemoveDuplicatedAttr(RuleSetChecker): 7 | 8 | '''{ 9 | "summary":"删除重复的属性设置", 10 | "desc":"如果在一个规则集中,对相同的两个属性进行了赋值,而且取值相同,则可以删除前面的赋值,例如: 11 |
12 | .test {
13 |     width: 100px;
14 |     width: 100px;
15 | }
16 | ==>
17 | .test {
18 |     width: 100px;
19 | }" 20 | }''' 21 | 22 | def __init__(self): 23 | self.id = 'remove-duplicated-attr' 24 | self.errorLevel = ERROR_LEVEL.ERROR 25 | self.errorMsg = 'has more than 1 ${name} in "${selector}"' 26 | 27 | def check(self, ruleSet, config): 28 | rules = ruleSet.getRules() 29 | collector = [] 30 | for rule in rules: 31 | info = self.ruleInfo(rule) 32 | if info in collector: 33 | return False 34 | collector.append(info) 35 | return True 36 | 37 | def fix(self, ruleSet, config): 38 | # make sure we use the last statement, so reverse and filter and reverse again 39 | # [a1, a2, b, c] ==> [c, b, a2, a1] ==> [c, b, a2] ==> [a2, b, c] 40 | rules = ruleSet.getRules() 41 | rules.reverse() 42 | newRules = [] 43 | collector = [] 44 | for rule in rules: 45 | info = self.ruleInfo(rule) 46 | if not info in collector: 47 | collector.append(info) 48 | newRules.append(rule) 49 | newRules.reverse() 50 | ruleSet.setRules(newRules) 51 | 52 | def ruleInfo(self, rule): 53 | if rule.fixedName != '': 54 | return rule.fixedName + ':' + rule.fixedValue 55 | return rule.strippedName + ':' + rule.strippedValue 56 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDStyleShouldInOrder.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .helper import getAttrOrder 5 | from .Base import * 6 | 7 | class FEDStyleShouldInOrder(RuleSetChecker): 8 | 9 | '''{ 10 | "summary":"属性应该按照推荐的顺序编写", 11 | "desc":"相同的CSS属性,如果按照推荐的顺序来编写,浏览器的处理性能会更高,推荐的顺序一般为:
12 | 显示属性 => 盒模型属性 => 背景/行高 => 文本属性 => 其他" 13 | }''' 14 | 15 | def __init__(self): 16 | self.id = 'keep-in-order' 17 | self.errorLevel = ERROR_LEVEL.WARNING 18 | self.errorMsg_rough = '"%s" should after "%s" in "${selector}" (order: display/box/text/other/css3)' 19 | self.errorMsg = '' 20 | 21 | def check(self, ruleSet, config): 22 | rules = ruleSet.getRules() 23 | if len(rules) < 2: 24 | return True 25 | 26 | order = self._generateNameOrderMapping(rules) 27 | length = len(order) 28 | for i in range(length): 29 | if i == length - 1: 30 | break; 31 | current = order[i] 32 | nextAttr = order[i + 1] 33 | 34 | if current[0] > nextAttr[0]: 35 | self.errorMsg = self.errorMsg_rough % (current[1], nextAttr[1]) 36 | return False 37 | 38 | return True 39 | 40 | def fix(self, ruleSet, config): 41 | rules = ruleSet.getRules() 42 | if len(rules) < 2: 43 | return True 44 | 45 | def comp(a, b): 46 | return a[0] - b[0] 47 | 48 | mapping = self._generateNameRuleMapping(rules) 49 | mapping.sort(comp) 50 | sortedRules = [] 51 | for x in range(len(mapping)): 52 | sortedRules.append(mapping[x][1]) 53 | ruleSet.setRules(sortedRules) 54 | 55 | def _generateNameOrderMapping(self, rules): 56 | return [(getAttrOrder(rule.name, rule.strippedName), rule.strippedName) for rule in rules] 57 | 58 | def _generateNameRuleMapping(self, rules): 59 | return [(getAttrOrder(rule.name, rule.strippedName), rule) for rule in rules] 60 | -------------------------------------------------------------------------------- /tests/unit/check/FEDSingleLineBraces.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 2: should have "only one space" before the opening brace in ".no-space-after-selector" 6 | 2: should have "only one space" before the opening brace in ".too-many-space-after-selector" 7 | 2: should have "only one space" after the opening brace in ".no-space-after-first-brace" 8 | 2: should have "only one space" after the opening brace in ".too-many-space-after-first-brace" 9 | 10 | 2: should have "only one space" before the closing brace in ".no-space-before-closing" 11 | 2: should have "only one space" before the closing brace in ".too-space-before-closing" 12 | 13 | 2: should start with "space" in ".test-space-before-selector" 14 | 15 | 2: should have one "space" before "width" in ".no-space-after-first-brace" 16 | 2: selector should end with only one space ".no-space-after-selector" 17 | 2: selector should end with only one space ".too-many-space-after-selector" 18 | 19 | 1: ".correct-single-line-styles, .multi-selector, .multi-selectorb, .no-space-before-closing, .too-space-before-closing, .multia, .multib" contains the same rules in "FEDSingleLineBraces.css" 20 | 1: ".no-space-after-selector, .too-many-space-after-selector, .test-space-before-selector, .no-space-after-first-brace, .too-many-space-after-first-brace" contains the same rules in "FEDSingleLineBraces.css" 21 | } 22 | 23 | .correct-single-line-styles { width: 100px; height: 100px; } 24 | 25 | .multi-selector, 26 | .multi-selectorb { width: 100px; height: 100px; } 27 | 28 | .no-space-after-selector{ width: 100px; } 29 | .too-many-space-after-selector { width: 100px; } 30 | .test-space-before-selector { width: 100px; } 31 | 32 | .no-space-after-first-brace {width: 100px; } 33 | .too-many-space-after-first-brace { width: 100px; } 34 | 35 | .no-space-before-closing { width: 100px; height: 100px;} 36 | .too-space-before-closing { width: 100px; height: 100px; } 37 | 38 | .multia, 39 | .multib { width: 100px; height: 100px; } 40 | -------------------------------------------------------------------------------- /tests/unit/entity/RuleSet.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _ruleSet() 5 | 6 | def _ruleSet(): 7 | ruleSet = RuleSet(' .test ', ' width:100px;height:100px; ', '/* aa */ ', None) 8 | ok(not ruleSet.extra, 'ruleset is not extra') 9 | equal(ruleSet.selector, '.test', 'selector is ok') 10 | equal(ruleSet.roughSelector, ' .test ', 'roughSelector is ok') 11 | equal(ruleSet.roughValue, ' width:100px;height:100px; ', 'roughValue is ok') 12 | equal(ruleSet.roughComment, '/* aa */ ', 'rough comment is ok') 13 | equal(ruleSet.values, 'width:100px;height:100px;', 'values is ok') 14 | ok(ruleSet.singleLineFlag, 'it is single line') 15 | ok(ruleSet.getSingleLineFlag(), 'it is single line') 16 | equal(ruleSet.getStyleSheet(), None, 'no stylesheet') 17 | 18 | equal(len(ruleSet.getRules()), 0, 'no rules') 19 | equal(ruleSet.indexOf('width'), -1, 'no _width') 20 | equal(ruleSet.existNames('width'), False, 'no width again') 21 | equal(ruleSet.existNames(' _width '), False, 'no rough _width') 22 | equal(ruleSet.getRuleByName('width'), None, 'can not find width') 23 | equal(ruleSet.getRuleByRoughName(' _width '), None, 'can not find _width') 24 | 25 | ruleSet.addRuleByStr(' .aaa', ' _width ', ' 100px; ') 26 | 27 | equal(len(ruleSet.getRules()), 1, 'one rule') 28 | equal(ruleSet.indexOf('_width'), 0, 'found width') 29 | equal(ruleSet.existNames('width'), True, 'found width again') 30 | equal(ruleSet.existRoughNames(' _width '), True, 'found rough width') 31 | equal(ruleSet.getRuleByName('width').value, '100px', 'find width') 32 | equal(ruleSet.getRuleByRoughName(' _width ').value, '100px', 'find width by rough name') 33 | equal(ruleSet.getRuleByStrippedName('_width').value, '100px', 'find width by stripped name') 34 | 35 | ruleSet.addRuleByStr(' .aaa', 'height', '100px; ') 36 | equal(len(ruleSet.getRules()), 2, 'two rules') 37 | equal(ruleSet.getRules()[0].name, 'width', 'width is first') 38 | equal(ruleSet.getRules()[1].name, 'height', 'height is second') 39 | -------------------------------------------------------------------------------- /tests/unit/fix/FixComplicatedStatement.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _handle_complicated_statement() 5 | 6 | def _handle_complicated_statement(): 7 | css = '''.ui-bar-a { 8 | border: 1px solid #333 /*{a-bar-border}*/; 9 | background: #111 /*{a-bar-background-color}*/; 10 | color: #fff /*{a-bar-color}*/; 11 | font-weight: bold; 12 | text-shadow: 0 /*{a-bar-shadow-x}*/ -1px /*{a-bar-shadow-y}*/ 1px /*{a-bar-shadow-radius}*/ #000 /*{a-bar-shadow-color}*/; 13 | background-image: -webkit-gradient(linear, left top, left bottom, from( #3c3c3c /*{a-bar-background-start}*/), to( #111 /*{a-bar-background-end}*/)); /* Saf4+, Chrome */ 14 | background-image: -webkit-linear-gradient( #3c3c3c /*{a-bar-background-start}*/, #111 /*{a-bar-background-end}*/); /* Chrome 10+, Saf5.1+ */ 15 | background-image: -moz-linear-gradient( #3c3c3c /*{a-bar-background-start}*/, #111 /*{a-bar-background-end}*/); /* FF3.6 */ 16 | background-image: -ms-linear-gradient( #3c3c3c /*{a-bar-background-start}*/, #111 /*{a-bar-background-end}*/); /* IE10 */ 17 | background-image: -o-linear-gradient( #3c3c3c /*{a-bar-background-start}*/, #111 /*{a-bar-background-end}*/); /* Opera 11.10+ */ 18 | background-image: linear-gradient( #3c3c3c /*{a-bar-background-start}*/, #111 /*{a-bar-background-end}*/); 19 | } 20 | 21 | .ui-bar-a .ui-link-inherit { 22 | color: #fff /*{a-bar-color}*/; 23 | }''' 24 | 25 | expectedFixed = '''.ui-bar-a { 26 | border: 1px solid #333; 27 | background: #111; 28 | background-image: -webkit-gradient(linear,left top,left bottom,from(#3C3C3C),to(#111)); 29 | background-image: -webkit-linear-gradient(#3C3C3C,#111); 30 | background-image: -moz-linear-gradient(#3C3C3C,#111); 31 | background-image: -ms-linear-gradient(#3C3C3C,#111); 32 | background-image: -o-linear-gradient(#3C3C3C,#111); 33 | background-image: linear-gradient(#3C3C3C,#111); 34 | color: #FFF; 35 | font-weight: bold; 36 | text-shadow: 0 -1px 1px #000; 37 | } 38 | 39 | .ui-bar-a .ui-link-inherit { 40 | color: #FFF; 41 | }''' 42 | 43 | fixer, msg = doFix(css, '') 44 | equal(msg.strip(), expectedFixed.strip(), 'complicated statement is ok') -------------------------------------------------------------------------------- /editor-plugins/fixstyle.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: fixstyle.vim 3 | " Author: zhifu.wang(wangjeaf@gmail.com) 4 | " WebPage: https://github.com/wangjeaf/CSSCheckStyle 5 | " License: MIT 6 | if &cp || exists("loaded_fixcssstyle") 7 | finish 8 | endif 9 | let loaded_fixcssstyle = 1 10 | 11 | function! s:is_css() 12 | return expand("%:e") == "css" 13 | endfunction 14 | 15 | function! g:Fixstyle(a) 16 | if !s:is_css() 17 | echo "Not a CSS file." 18 | return 19 | endif 20 | 21 | let ret = system("ckstyle fix -p ".expand('%:t')) 22 | 23 | :g/.*/d 24 | let @0 = ret 25 | :put!0 26 | endfunction 27 | 28 | function! g:FixstyleSingleLine(a) 29 | if !s:is_css() 30 | echo "Not a CSS file." 31 | return 32 | endif 33 | 34 | let ret = system("ckstyle fix -p --singleLine ".expand('%:t')) 35 | 36 | :g/.*/d 37 | let @0 = ret 38 | :put!0 39 | endfunction 40 | 41 | function! g:FixstyleSafe(a) 42 | if !s:is_css() 43 | echo "Not a CSS file." 44 | return 45 | endif 46 | 47 | let ret = system("ckstyle fix -p --safeMode ".expand('%:t')) 48 | 49 | :g/.*/d 50 | let @0 = ret 51 | :put!0 52 | endfunction 53 | 54 | function! g:Ckstyle(a) 55 | if !s:is_css() 56 | echo "Not a CSS file." 57 | return 58 | endif 59 | 60 | let ret = system("ckstyle check ".expand('%:t')) 61 | if filereadable("".expand('%:t').".ckstyle.txt") 62 | echo "[ckstyle] has error, @see ".expand('%:t').".ckstyle.txt" 63 | else 64 | echo "[ckstyle] No mistake found in this CSS, NB!" 65 | endif 66 | 67 | endfunction 68 | 69 | function! g:CssCompress(a) 70 | if !s:is_css() 71 | echo "Not a CSS file." 72 | return 73 | endif 74 | 75 | let ret = system("ckstyle compress -p ".expand('%:t')) 76 | 77 | :g/.*/d 78 | let @0 = ret 79 | :put!0 80 | endfunction 81 | 82 | command! -nargs=? -range=% Fixstyle :call g:Fixstyle(, ) 83 | command! -nargs=? -range=% FixstyleSafe :call g:FixstyleSafe(, ) 84 | command! -nargs=? -range=% FixstyleSingleLine :call g:FixstyleSingleLine(, ) 85 | command! -nargs=? -range=% CssCompress :call g:CssCompress(, ) 86 | command! -nargs=? -range=% Ckstyle :call g:Ckstyle(, ) 87 | 88 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDUnknownHTMLTagName.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isHTMLTag 6 | 7 | class FEDUnknownHTMLTagName(RuleSetChecker): 8 | 9 | '''{ 10 | "summary":"错误的HTML Tag", 11 | "desc":"如果您输入了错误的HTML Tag,本工具也会给出响应的提示" 12 | }''' 13 | 14 | def __init__(self): 15 | self.id = 'unknown-html-tag' 16 | self.errorLevel = ERROR_LEVEL.ERROR 17 | self.errorMsg_rough = 'unknown html tag "%s" found in "${selector}"' 18 | self.errorMsg = '' 19 | 20 | def check(self, ruleSet, config): 21 | selector = ruleSet.selector.lower() 22 | if selector.find('@media') != -1: 23 | return True 24 | if selector.find('@-moz-document') != -1: 25 | return True 26 | selectors = selector.split(',') 27 | for s in selectors: 28 | for r in s.split(' '): 29 | r = r.strip() 30 | if r != '': 31 | if r.find('::') != -1: 32 | # p::selection 33 | tag = r.split('::')[0].split('.')[0].split('#')[0].strip() 34 | else: 35 | # abcd:hover 36 | # abcd.class-name:hover 37 | # abcd#class-name:hover 38 | tag = r.split(':')[0].split('.')[0].split('#')[0].strip() 39 | 40 | # .test > .inner 41 | if tag == '' or tag == '>' or tag == '*' or tag == '+': 42 | continue 43 | 44 | # #id 45 | if tag.find('#') != -1: 46 | continue 47 | 48 | # input[type=button] 49 | if tag.find('[') != -1: 50 | tag = tag.split('[')[0].strip() 51 | 52 | # *+html 53 | if tag.startswith('*+'): 54 | tag = tag[2:] 55 | 56 | # *html 57 | elif tag.startswith('*'): 58 | tag = tag[1:] 59 | 60 | if not isHTMLTag(tag): 61 | self.errorMsg = self.errorMsg_rough % tag 62 | return False 63 | return True 64 | -------------------------------------------------------------------------------- /ckstyle/entity/EntityUtil.py: -------------------------------------------------------------------------------- 1 | from ckstyle.browsers.BinaryRule import ALL 2 | 3 | import re 4 | replacer1 = re.compile('\s*{\s*') 5 | replacer2 = re.compile('\s*:\s*') 6 | replacer3 = re.compile('\s*;\s*}\s*') 7 | replacer4 = re.compile('\s*;\s*') 8 | replacer5 = re.compile('\s\s+') 9 | replacer6 = re.compile('\(\s+') 10 | replacer7 = re.compile('\s+\)') 11 | replacer8 = re.compile('\s+,') 12 | replacer9 = re.compile(',\s+') 13 | 14 | class Cleaner(): 15 | @staticmethod 16 | def clean(msg): 17 | msg = msg.strip().replace('\r', '').replace('\n', '').replace(' ' * 4, ' ') 18 | msg = replacer1.sub('{', msg) 19 | msg = replacer2.sub(':', msg) 20 | msg = replacer3.sub('}', msg) 21 | msg = replacer4.sub(';', msg) 22 | msg = replacer5.sub(' ', msg) 23 | msg = replacer6.sub('(', msg) 24 | msg = replacer7.sub(')', msg) 25 | msg = replacer8.sub(',', msg) 26 | msg = replacer9.sub(',', msg) 27 | msg = msg.strip() 28 | return msg 29 | 30 | @staticmethod 31 | def clearName(name): 32 | name = name.strip() 33 | # #padding: 10px??? 34 | if name.startswith('_') or name.startswith('*') or name.startswith('+') or name.startswith('#'): 35 | name = name[1:] 36 | if name.startswith('-'): 37 | if name.startswith('-moz-') or name.startswith('-webkit-') or name.startswith('-ms-') or name.startswith('-o-') or name.startswith('-khtml-'): 38 | name = '-'.join(name.split('-')[2:]) 39 | return name.lower() 40 | 41 | @staticmethod 42 | def clearValue(value): 43 | value = value.strip() 44 | if value.endswith(';'): 45 | value = value[0: - 1] 46 | return value 47 | 48 | @staticmethod 49 | def clearValues(values): 50 | values = values.strip() 51 | return values 52 | 53 | @staticmethod 54 | def clearSelector(selector): 55 | return ' '.join(selector.split('\n')).strip() 56 | 57 | @staticmethod 58 | def clearComment(comment): 59 | comment = comment.strip() 60 | if len(comment) != 0 and comment.find('\n') == -1: 61 | comment = comment.replace('/*', '').replace('*/', '').strip() 62 | comment = '/* ' + comment + ' */' 63 | return comment 64 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDNoUnitAfterZero.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | import re 6 | 7 | pattern_unit = re.compile(r'(0\s*[\w]+)') 8 | replacer_unit = re.compile(',\s+') 9 | 10 | class FEDNoUnitAfterZero(RuleChecker): 11 | 12 | '''{ 13 | "summary":"删除0后面的单位", 14 | "desc":"0后面的单位可以删除,以实现更好的压缩。比如 0px ==> 00em ==> 0 等, 15 | 但是transition: 0ss不能省略" 16 | }''' 17 | 18 | def __init__(self): 19 | self.id = 'del-unit-after-zero' 20 | self.errorLevel = ERROR_LEVEL.WARNING 21 | self.errorMsg = 'unit should be removed when meet 0 in "${selector}"' 22 | 23 | def check(self, rule, config): 24 | 25 | values = rule.value.split(' ') 26 | 27 | for v in values: 28 | v = v.strip() 29 | if v.find('(') != -1: 30 | matched = self._startsWithZero(v.split('(')[1]) 31 | else: 32 | matched = self._startsWithZero(v) 33 | 34 | if matched is None: 35 | continue 36 | 37 | for m in matched: 38 | if m != '0s': 39 | return False 40 | 41 | return True 42 | 43 | def fix(self, rule, config): 44 | if rule.name == 'expression': 45 | return 46 | 47 | fixed = rule.fixedValue 48 | rule.fixedValue = rule.fixedValue.replace(',', ', ') 49 | 50 | collector = [] 51 | for v in rule.fixedValue.split(' '): 52 | v = v.strip() 53 | if v.find('(') != -1: 54 | matched = self._startsWithZero(v.split('(')[1]) 55 | else: 56 | matched = self._startsWithZero(v) 57 | 58 | if matched is None: 59 | collector.append(v) 60 | continue 61 | 62 | finalV = v; 63 | for m in matched: 64 | if m != '0s': 65 | finalV = finalV.replace(m, '0') 66 | collector.append(finalV) 67 | 68 | rule.fixedValue = replacer_unit.sub(', ', ' '.join(collector)) 69 | 70 | def _startsWithZero(self, value): 71 | matcher = pattern_unit.match(value) 72 | if matcher is not None: 73 | return matcher.groups() 74 | return None 75 | -------------------------------------------------------------------------------- /ckstyle/plugins/FEDMultiLineSpaces.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | 4 | from .Base import * 5 | from .helper import isCss3PrefixProp 6 | 7 | class FEDMultiLineSpaces(RuleChecker): 8 | 9 | '''{ 10 | "summary":"CSS多行风格的空格检查", 11 | "desc":"多行风格下,CSS的空格检查包括: 12 |
    13 |
  1. 选择器的空格
  2. 14 |
  3. 属性的空格
  4. 15 |
  5. 结尾}的空格
  6. 16 |
17 | 具体请参见人人相关的CSS规范" 18 | }''' 19 | 20 | def __init__(self): 21 | self.id = 'multi-line-space' 22 | self.errorLevel = ERROR_LEVEL.LOG 23 | self.errorMsg_name_pre = 'should have 4 spaces before "${name}" in "${selector}"' 24 | self.errorMsg_name_after = 'should not have "space" after "${name}" in "${selector}"' 25 | self.errorMsg_value_pre = 'should have (only) one "space" before value of "${name}" in "${selector}"' 26 | self.errorMsg = '' 27 | 28 | def check(self, rule, config): 29 | singleLine = rule.getRuleSet().getSingleLineFlag() 30 | if singleLine: 31 | return True 32 | 33 | prefix = ' ' * 4 34 | name = rule.roughName 35 | value = rule.roughValue 36 | stripped = rule.roughName.strip() 37 | 38 | # leave special css3 props for FEDCss3AttrChecker 39 | if isCss3PrefixProp(rule.name): 40 | if name.endswith(' '): 41 | self.errorMsg = self.errorMsg_name_after 42 | return False 43 | 44 | if not value.startswith(' ') or value.startswith(' '): 45 | self.errorMsg = self.errorMsg_value_pre 46 | return False 47 | 48 | return True 49 | 50 | if name.find('\t') != -1: 51 | name = name.replace('\t', prefix) 52 | if not name.startswith(prefix): 53 | self.errorMsg = self.errorMsg_name_pre 54 | return False 55 | if name.startswith(' ' * 5): 56 | self.errorMsg = self.errorMsg_name_pre 57 | return False 58 | if name.endswith(' '): 59 | self.errorMsg = self.errorMsg_name_after 60 | return False 61 | 62 | if not value.startswith(' ') or value.startswith(' '): 63 | self.errorMsg = self.errorMsg_value_pre 64 | return False 65 | 66 | return True 67 | -------------------------------------------------------------------------------- /ckstyle/cssparser/helper.py: -------------------------------------------------------------------------------- 1 | def isAlphaChar(char): 2 | return 97 <= ord(char) <= 122 3 | 4 | specialTexts = [ 5 | {'start':'@', 'text':'@import', 'end':';\n'}, 6 | {'start':'@', 'text':'@charset', 'end':';\n'}, 7 | {'start':'@', 'text':'@namespace', 'end':';'}, 8 | {'start':'@', 'text':'@-css-compiler ', 'end':'}'}, 9 | {'start':'@', 'text':'@-css-compiler{', 'end':'}'}, 10 | {'start':'@', 'text':'@-css-compiler-', 'end':';\n'}] 11 | 12 | specialStartChars = set([x['start'] for x in specialTexts]) 13 | 14 | nestedStatements = ['keyframes', '@media', '@-moz-document'] 15 | 16 | def isSpecialStart(char): 17 | for x in specialStartChars: 18 | if x == char: 19 | return True 20 | return False 21 | 22 | def isNestedStatement(selector): 23 | if selector.find('@') == -1: 24 | return False 25 | for x in nestedStatements: 26 | if selector.find(x) != -1: 27 | return True 28 | return False 29 | 30 | def handleSpecialStatement(text, i, length, char): 31 | for obj in specialTexts: 32 | if char == obj['start'] and isSpecialString(text, i, obj["text"]): 33 | a, b = findCharFrom(text, i, length, obj["end"]) 34 | return a, b, obj["text"] 35 | return None, None, None 36 | 37 | def findCharFrom(text, i, length, left, right = None): 38 | counter = 1 39 | collector = '' 40 | for j in range(i + 1, length): 41 | if right == None: 42 | if text[j] == left or left.find(text[j]) != -1: 43 | break; 44 | else: 45 | collector = collector + text[j] 46 | else: 47 | if text[j] == left: 48 | collector = collector + text[j] 49 | counter = counter + 1 50 | elif text[j] == right: 51 | collector = collector + text[j] 52 | counter = counter - 1 53 | if counter == 0: 54 | break; 55 | else: 56 | collector = collector + text[j] 57 | return j, collector 58 | 59 | def isSpecialString(text, i, string): 60 | return text[i: i + len(string)] == string 61 | 62 | def isCommentStart(char, text, i): 63 | return char == '/' and i + 1 < len(text) and text[i + 1] == '*' 64 | 65 | def isCommentEnd(char, text, i): 66 | return char == '/' and text[i - 1] == '*' 67 | 68 | -------------------------------------------------------------------------------- /ckstyle/entity/StyleSheet.py: -------------------------------------------------------------------------------- 1 | from .RuleSet import RuleSet 2 | from .ExtraStatement import ExtraStatement 3 | from .NestedStatement import NestedStatement 4 | from .EntityUtil import ALL 5 | 6 | class StyleSheet(): 7 | def __init__(self, fileName = ''): 8 | self._ruleSets = []; 9 | self._file = fileName 10 | self.browser = ALL 11 | self.toBeUsed = {} 12 | 13 | def addRuleSetByStr(self, selector, attrs, comment): 14 | self._ruleSets.append(RuleSet(selector, attrs, comment, self)) 15 | 16 | def addExtraStatement(self, operator, statement, comment): 17 | self._ruleSets.append(ExtraStatement(operator, statement, comment, self)) 18 | 19 | def addNestedRuleSet(self, selector, attrs, comment): 20 | self._ruleSets.append(NestedStatement(selector, attrs, comment, self)) 21 | 22 | def setFile(self, fileName): 23 | self._file = fileName 24 | 25 | def getFile(self): 26 | return self._file 27 | 28 | def getRuleSets(self): 29 | return self._ruleSets 30 | 31 | def removeRuleSetByIndex(self, index): 32 | self._ruleSets[index] = None 33 | 34 | def removeRuleSet(self, ruleSet): 35 | newRuleSets = [] 36 | for x in self._ruleSets: 37 | if x == ruleSet: 38 | continue 39 | newRuleSets.append(x) 40 | self._ruleSets = newRuleSets 41 | 42 | def clean(self): 43 | newRuleSets = [] 44 | for x in self._ruleSets: 45 | if x is None: 46 | continue 47 | newRuleSets.append(x) 48 | self._ruleSets = newRuleSets 49 | 50 | def getRuleSetBySelector(self, selector): 51 | for ruleSet in self._ruleSets: 52 | if ruleSet.selector == selector: 53 | return ruleSet 54 | 55 | def compress(self, browser = ALL): 56 | result = [] 57 | for ruleSet in self._ruleSets: 58 | if not ruleSet.browser & browser: 59 | continue 60 | result.append(ruleSet.compress(browser)) 61 | return ''.join(result) 62 | 63 | def fixed(self, config): 64 | result = [] 65 | for ruleSet in self._ruleSets: 66 | result.append(ruleSet.fixed(config)) 67 | return '\n\n'.join(result) 68 | 69 | def rebase(self): 70 | for ruleSet in self._ruleSets: 71 | ruleSet.rebase() 72 | -------------------------------------------------------------------------------- /tests/unit/check/FEDHighPerformanceSelector.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @author: zhifu.wang@renren-inc.com 3 | */ 4 | @unit-test-expecteds { 5 | 0: please shorter the selector ".a .b .c .d .e .f" 6 | 0: use less tag in "body div ul li" 7 | 0: use less tag in "body .a .b li" 8 | 0: use less tag in "ul li" 9 | 0: use less tag in "ol li" 10 | 0: use less tag in "dl dt" 11 | 0: use less tag in "dl dd" 12 | 13 | 0: should not put "HTMLtag" and "#id" together in "div#test" 14 | 0: should not put "HTMLtag" and ".class" together in "body.class ul li" 15 | 0: should not put "HTMLtag" and ".class" together in "body.class ul .class .a" 16 | 0: should not put "HTMLtag" and ".class" together in "ul.test li" 17 | 18 | 0: should not use ~=,^=,|=,$=,*= in selector of ".div[class~=xxx]" 19 | 0: should not use ~=,^=,|=,$=,*= in selector of ".div[class^=xxx]" 20 | 0: should not use ~=,^=,|=,$=,*= in selector of ".div[class*=xxx]" 21 | 0: should not use ~=,^=,|=,$=,*= in selector of ".div[class|=xxx]" 22 | 0: should not use ~=,^=,|=,$=,*= in selector of ".div[class$=xxx]" 23 | 24 | 0: should not put "HTMLtag" and ".class" together in "div.class::selection" 25 | 26 | 1: ".div[class*=xxx], .div[class|=xxx], .div[class$=xxx], .div[class^=xxx], .div[class~=xxx], div#test, .a .b .c .d .e .f, body div ul li, body .a .b li, body.class ul li, body.class ul .class .a, ul.test li, ul li, ol li, dl dt, dl dd" contains the same rules in "FEDHighPerformanceSelector.css" 27 | } 28 | 29 | div.class::selection { 30 | color: red; 31 | } 32 | 33 | .div[class*=xxx] { 34 | width: 100px; 35 | } 36 | 37 | .div[class|=xxx] { 38 | width: 100px; 39 | } 40 | 41 | .div[class$=xxx] { 42 | width: 100px; 43 | } 44 | 45 | .div[class^=xxx] { 46 | width: 100px; 47 | } 48 | 49 | .div[class~=xxx] { 50 | width: 100px; 51 | } 52 | 53 | div#test { 54 | width: 100px; 55 | } 56 | 57 | .a .b .c .d .e .f { 58 | width: 100px; 59 | } 60 | 61 | body div ul li { 62 | width: 100px; 63 | } 64 | 65 | body .a .b li { 66 | width: 100px; 67 | } 68 | 69 | body.class ul li { 70 | width: 100px; 71 | } 72 | 73 | body.class ul .class .a { 74 | width: 100px; 75 | } 76 | 77 | ul.test li { 78 | width: 100px; 79 | } 80 | 81 | ul li { 82 | width: 100px; 83 | } 84 | 85 | ol li { 86 | width: 100px; 87 | } 88 | 89 | dl dt { 90 | width: 100px; 91 | } 92 | 93 | dl dd { 94 | width: 100px; 95 | } 96 | -------------------------------------------------------------------------------- /tests/unit/parser/ExtraStatement.py: -------------------------------------------------------------------------------- 1 | from helper import * 2 | 3 | def doTest(): 4 | _withEnter() 5 | _withoutEnter() 6 | 7 | def _withoutEnter(): 8 | parser = CssParser('@import url(xxxx);@charset utf-8;.test {_width : 100px;}') 9 | parser.doParse() 10 | ok(parser is not None, 'parser is not None') 11 | ok(parser.styleSheet is not None, 'parser.styleSheet is not None') 12 | equal(len(parser.styleSheet.getRuleSets()), 3, 'three rule set') 13 | 14 | styleSheet = parser.styleSheet 15 | importer = styleSheet.getRuleSets()[0] 16 | ok(importer.extra, 'import is extra') 17 | ok(importer.isImport(), 'it is import') 18 | equal(importer.operator, '@import', 'it is @import') 19 | equal(importer.statement, '@import url(xxxx);', 'statement is ok') 20 | 21 | charset = styleSheet.getRuleSets()[1] 22 | ok(charset.extra, 'charset is extra') 23 | ok(not charset.isImport(), 'it is not import') 24 | equal(charset.operator, '@charset', 'it is @charset') 25 | equal(charset.statement, '@charset utf-8;', 'statement is ok') 26 | 27 | rule = styleSheet.getRuleSets()[2] 28 | ok(not rule.extra, 'not extra') 29 | equal(rule.selector, '.test', 'selector is ok') 30 | equal(rule.getRuleByName('width').value, '100px', 'value is ok') 31 | 32 | def _withEnter(): 33 | parser = CssParser('@import url(xxxx);\n@charset utf-8;\n.test {_width : 100px;}') 34 | parser.doParse() 35 | ok(parser is not None, 'parser is not None') 36 | ok(parser.styleSheet is not None, 'parser.styleSheet is not None') 37 | equal(len(parser.styleSheet.getRuleSets()), 3, 'three rule set') 38 | 39 | styleSheet = parser.styleSheet 40 | importer = styleSheet.getRuleSets()[0] 41 | ok(importer.extra, 'import is extra') 42 | ok(importer.isImport(), 'it is import') 43 | equal(importer.operator, '@import', 'it is @import') 44 | equal(importer.statement, '@import url(xxxx);', 'statement is ok') 45 | 46 | charset = styleSheet.getRuleSets()[1] 47 | ok(charset.extra, 'charset is extra') 48 | ok(not charset.isImport(), 'it is not import') 49 | equal(charset.operator, '@charset', 'it is @charset') 50 | equal(charset.statement, '@charset utf-8;', 'statement is ok') 51 | 52 | rule = styleSheet.getRuleSets()[2] 53 | ok(not rule.extra, 'not extra') 54 | equal(rule.selector, '.test', 'selector is ok') 55 | equal(rule.getRuleByName('width').value, '100px', 'value is ok') 56 | -------------------------------------------------------------------------------- /utils/exportRules.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python 2 | #encoding=utf-8 3 | import os 4 | import json 5 | 6 | def loadPlugins(pluginDir): 7 | ids = [] 8 | tmp = '{id:"%s", priority:%s, summary:"%s", desc:"%s", checked:%s}'; 9 | 10 | '''从plugins目录动态载入检查类''' 11 | for filename in os.listdir(pluginDir): 12 | if not filename.endswith('.py') or filename.startswith('_'): 13 | continue 14 | if filename == 'Base.py' or filename == 'helper.py': 15 | continue 16 | pluginName = os.path.splitext(filename)[0] 17 | 18 | # 获取plugins的引用 19 | plugin = __import__("ckstyle.plugins." + pluginName, fromlist = [pluginName]) 20 | pluginClass = None 21 | if hasattr(plugin, pluginName): 22 | pluginClass = getattr(plugin, pluginName) 23 | else: 24 | console.error('[TOOL] class %s should exist in %s.py' % (pluginName, pluginName)) 25 | continue 26 | # 构造plugin的类 27 | instance = pluginClass() 28 | if (hasattr(instance, 'private') and getattr(instance, 'private') is True): 29 | continue 30 | obj = {} 31 | obj["id"] = instance.id 32 | obj["priority"] = instance.errorLevel 33 | obj["desc"] = pluginClass.__name__ 34 | obj["summary"] = pluginClass.__name__ 35 | doc = pluginClass.__doc__ 36 | 37 | data = None 38 | if (doc != None): 39 | doc = doc.replace('\n', '') 40 | doc = doc.replace('\t', '') 41 | doc = doc.replace(' ', '') 42 | try: 43 | data = json.loads(doc) 44 | except Exception, e: 45 | print '[JSON ERROR] doc error in ' + pluginClass.__name__ + ', not json format' 46 | obj["desc"] = pluginClass.__doc__ 47 | if data is not None: 48 | if data.has_key('summary') and data['summary'] != 'xxx': 49 | obj['summary'] = data['summary'].encode('utf-8') 50 | if data.has_key('desc') and data['desc'] != 'xxx': 51 | obj['desc'] = data['desc'].encode('utf-8') 52 | 53 | obj["checked"] = 'true' if (instance.errorLevel == 0 or instance.errorLevel == 1) else 'false' 54 | ids.append(tmp % (obj["id"], obj["priority"], obj["summary"], obj["desc"], obj["checked"])) 55 | 56 | open('D:/git/CSSCheckStyle-website/js/rules.js', 'w').write('var RULES = [\n ' + (',\n '.join(ids)) + '\n];'); 57 | 58 | loadPlugins('../ckstyle/plugins') --------------------------------------------------------------------------------