├── README.md
├── demo-build.html
├── demo-server.html
├── help.html
├── lib
├── css_inject.js
├── dep_tpl.js
├── modules.js
├── pkg_tpl.js
└── pyjs.js
├── manifest.json
├── plugins
├── __init__.py
├── __init__.pyc
├── combo.py
└── combo.pyc
├── pyjs.py
├── pyjs
├── __init__.py
├── __init__.pyc
├── localserver.py
├── localserver.pyc
├── parser.py
├── parser.pyc
├── utils.py
└── utils.pyc
├── server.json
├── src
├── core
│ ├── __init__.js
│ ├── init.js
│ └── prelude.js
├── increment.js
├── main
│ ├── __init__.js
│ ├── one.js
│ └── two.js
└── math.js
├── tools
├── .svn
│ ├── all-wcprops
│ └── entries
├── closure_linter
│ ├── __init__.py
│ ├── checker.py
│ ├── checkerbase.py
│ ├── closurizednamespacesinfo.py
│ ├── closurizednamespacesinfo_test.py
│ ├── common
│ │ ├── __init__.py
│ │ ├── error.py
│ │ ├── erroraccumulator.py
│ │ ├── errorhandler.py
│ │ ├── errorprinter.py
│ │ ├── filetestcase.py
│ │ ├── htmlutil.py
│ │ ├── lintrunner.py
│ │ ├── matcher.py
│ │ ├── position.py
│ │ ├── simplefileflags.py
│ │ ├── tokenizer.py
│ │ └── tokens.py
│ ├── ecmalintrules.py
│ ├── ecmametadatapass.py
│ ├── error_check.py
│ ├── error_fixer.py
│ ├── errorrules.py
│ ├── errors.py
│ ├── fixjsstyle.py
│ ├── fixjsstyle_test.py
│ ├── full_test.py
│ ├── gjslint.py
│ ├── indentation.py
│ ├── javascriptlintrules.py
│ ├── javascriptstatetracker.py
│ ├── javascripttokenizer.py
│ ├── javascripttokens.py
│ ├── not_strict_test.py
│ ├── requireprovidesorter.py
│ ├── statetracker.py
│ └── tokenutil.py
└── jsdoc
│ ├── README.txt
│ ├── app
│ ├── frame.js
│ ├── frame
│ │ ├── Chain.js
│ │ ├── Dumper.js
│ │ ├── Hash.js
│ │ ├── Link.js
│ │ ├── Namespace.js
│ │ ├── Opt.js
│ │ ├── Reflection.js
│ │ ├── String.js
│ │ └── Testrun.js
│ ├── handlers
│ │ ├── FOODOC.js
│ │ ├── XMLDOC.js
│ │ └── XMLDOC
│ │ │ ├── DomReader.js
│ │ │ ├── XMLDoc.js
│ │ │ └── XMLParse.js
│ ├── lib
│ │ ├── JSDOC.js
│ │ └── JSDOC
│ │ │ ├── DocComment.js
│ │ │ ├── DocTag.js
│ │ │ ├── JsDoc.js
│ │ │ ├── JsPlate.js
│ │ │ ├── Lang.js
│ │ │ ├── Parser.js
│ │ │ ├── PluginManager.js
│ │ │ ├── Symbol.js
│ │ │ ├── SymbolSet.js
│ │ │ ├── TextStream.js
│ │ │ ├── Token.js
│ │ │ ├── TokenReader.js
│ │ │ ├── TokenStream.js
│ │ │ ├── Util.js
│ │ │ └── Walker.js
│ ├── main.js
│ ├── plugins
│ │ ├── commentSrcJson.js
│ │ ├── frameworkPrototype.js
│ │ ├── functionCall.js
│ │ ├── publishSrcHilite.js
│ │ ├── symbolLink.js
│ │ ├── tagParamConfig.js
│ │ └── tagSynonyms.js
│ ├── run.js
│ ├── t
│ │ ├── TestDoc.js
│ │ └── runner.js
│ ├── test.js
│ └── test
│ │ ├── addon.js
│ │ ├── anon_inner.js
│ │ ├── augments.js
│ │ ├── augments2.js
│ │ ├── borrows.js
│ │ ├── borrows2.js
│ │ ├── config.js
│ │ ├── constructs.js
│ │ ├── encoding.js
│ │ ├── encoding_other.js
│ │ ├── event.js
│ │ ├── exports.js
│ │ ├── functions_anon.js
│ │ ├── functions_nested.js
│ │ ├── global.js
│ │ ├── globals.js
│ │ ├── ignore.js
│ │ ├── inner.js
│ │ ├── jsdoc_test.js
│ │ ├── lend.js
│ │ ├── memberof.js
│ │ ├── memberof2.js
│ │ ├── memberof3.js
│ │ ├── memberof_constructor.js
│ │ ├── module.js
│ │ ├── multi_methods.js
│ │ ├── name.js
│ │ ├── namespace_nested.js
│ │ ├── nocode.js
│ │ ├── oblit_anon.js
│ │ ├── overview.js
│ │ ├── param_inline.js
│ │ ├── params_optional.js
│ │ ├── prototype.js
│ │ ├── prototype_nested.js
│ │ ├── prototype_oblit.js
│ │ ├── prototype_oblit_constructor.js
│ │ ├── public.js
│ │ ├── scripts
│ │ ├── code.js
│ │ └── notcode.txt
│ │ ├── shared.js
│ │ ├── shared2.js
│ │ ├── shortcuts.js
│ │ ├── static_this.js
│ │ ├── synonyms.js
│ │ ├── tosource.js
│ │ └── variable_redefine.js
│ ├── changes.txt
│ ├── conf
│ └── sample.conf
│ ├── java
│ ├── build.xml
│ ├── build_1.4.xml
│ ├── classes
│ │ └── js.jar
│ └── src
│ │ ├── JsDebugRun.java
│ │ └── JsRun.java
│ ├── jsdebug.jar
│ ├── jsrun.jar
│ ├── jsrun.sh
│ └── templates
│ └── jsdoc
│ ├── allclasses.tmpl
│ ├── allfiles.tmpl
│ ├── class.tmpl
│ ├── index.tmpl
│ ├── publish.js
│ ├── static
│ ├── default.css
│ ├── header.html
│ └── index.html
│ └── symbol.tmpl
└── utest
├── index.html
├── js
└── pyjs.js
├── qunit.css
└── qunit.js
/README.md:
--------------------------------------------------------------------------------
1 | PyJs Javascript FrameWork
2 | =========================
3 |
4 | A commonjs like js framework base on Python.
5 |
6 | See help.html for more information.
7 |
8 | [doc](http://demix.github.com/pyjs/)
9 |
10 | [intro](http://www.cnblogs.com/demix/tag/pyjs/)
11 |
12 | Full test under Python 2.7
13 |
14 | Python 3 seems not supported......
15 |
--------------------------------------------------------------------------------
/demo-build.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
介绍
24 |
项目地址
25 |
PyJs是一种浏览器端的CommonJs实现
26 |
PyJs特别适用于一些只有静态文件构成的项目,最佳是只有js构成的项目,如baidu connect sdk.
27 |
28 |
使用
29 |
30 | -
31 |
pyjsdir
32 | pyjsdir是pyjs运行的核心文件夹,所有commonjs规范的js文件,必须放在pyjsdir中,默认为src,可在manifest.json中设置。
33 |
34 | -
35 |
本地服务器中使用pyjs
36 | 使用
37 |
38 | python pyjs.py runserver
39 |
40 | 启动本地服务器
41 | 测试页面中引入lib/pyjs.js
42 | 对于以.js结尾的文件,将查找相对路径,若相对路径下没有原文件,则搜索pyjsdir下文件或文件夹,若有文件与之相同,则编译包之后返回,replace为local。若有文件夹与去除.js部分相同,则查找文件夹下__init__.js文件,若有,按里面定义的__all__数据取出所有文件,编译之后返回。
43 |
44 | -
45 |
编译
46 | 上线前,运行
47 |
48 | python pyjs.py
49 |
50 | 会在build文件下生成所有commonjs模块文件。manifest中可以制定combo_url,若指定,运行编译后文件的require时,将通过combo请求文件。若没有指定,将同时加载多个js文件。
51 | 文件同时会生成boot.js,里面有文件的依赖关系,线上html引用这个文件即可。
52 |
53 |
54 |
55 |
manifest.json声明属性
56 |
57 | -
58 |
version
59 | type:string
60 | 版本号
61 |
62 | -
63 |
pyjsdir
64 | type:string
65 | pyjs包所在的文件夹,默认为src
66 |
67 | -
68 |
combo
69 | type:object
70 | 设置combo,属性combo_url标志combo目标url
71 |
72 | -
73 |
replace
74 | type:object
75 | token: %#xxx#%,将替换文件中%##%中间的字符
76 |
77 | "replace":{
78 | "replace_type":{
79 | "token1":"url1",
80 | "token2":"url2",
81 | "token3":"url3"
82 | }
83 | }
84 |
85 |
86 |
87 |
Local Transimission
88 |
本地server启动后,若配置server.json,会启用请求转发。
89 |
server.json声明分为两部分,key为正则,对应匹配url请求规则;value为映射
90 |
91 | -
92 |
映射到线上请求
93 | 所有以http开头的都会映射到线上请求
94 | 这里不会做权限控制等,有复杂需求使用自定义插件
95 |
96 | -
97 |
映射到本地插件
98 | 所有以plugins开头都会映射到本地插件,对应到plugins的相应module中。
99 | module文件必须包含main函数,会将请求的search参数传入到main函数中。
100 | 返回值: content-type , body
101 |
102 |
103 |
114 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/lib/css_inject.js:
--------------------------------------------------------------------------------
1 | var baidu = baidu || {};
2 | baidu.more = baidu.more || {};
3 |
4 | /**
5 | * 使用 style 标签添加一段stylesheet
6 | * @function
7 | * @name baidu.more.addCssRules
8 | * @param styles {string} 样式
9 | * @param names {array} 样式的name值
10 | */
11 | baidu.more.addCssRules = function(styles, names) {
12 | if (!baidu.more.addCssRules._cssRules) {
13 | baidu.more.addCssRules._cssRules = {};
14 | }
15 |
16 | // note, we potentially re-include CSS if it comes with other CSS that we
17 | // have previously not included.
18 | var allIncluded = true;
19 | baidu.each(names, function(id) {
20 | if (!(id in baidu.more.addCssRules._cssRules)) {
21 | allIncluded = false;
22 | baidu.more.addCssRules._cssRules[id] = true;
23 | }
24 | });
25 |
26 | if (allIncluded) {
27 | return;
28 | }
29 |
30 | if (!baidu.browser.ie) {
31 | var style = document.createElement('style');
32 | style.type = 'text/css';
33 | style.textContent = styles;
34 | document.getElementsByTagName('HEAD')[0].appendChild(style);
35 | } else {
36 | try {
37 | document.createStyleSheet().cssText = styles;
38 | } catch (exc) {
39 | // major problem on IE : You can only create 31 stylesheet objects with
40 | // this method. We will have to add the styles into an existing
41 | // stylesheet.
42 | if (document.styleSheets[0]) {
43 | document.styleSheets[0].cssText += styles;
44 | }
45 | }
46 | }
47 | };
48 |
49 |
--------------------------------------------------------------------------------
/lib/dep_tpl.js:
--------------------------------------------------------------------------------
1 | addDependence('##package##' , '##dependence##');
2 |
--------------------------------------------------------------------------------
/lib/modules.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | */
4 |
5 | /**
6 | *
7 | */
8 |
9 |
10 | define( "##package##" , function(require , exports , module){
11 |
12 | ##file##
13 |
14 | } );
--------------------------------------------------------------------------------
/lib/pkg_tpl.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | ##file##
4 |
5 | })();
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"",
3 | "version":"0.1.1",
4 | "parent":"lib/modules.js",
5 | "pyjsdir": "src",
6 |
7 | "replace":{
8 | "local":{
9 | "js_url":"http://demix.baidu.com/PyJs/build/"
10 | },
11 | "test":{
12 | "js_url":"http://demix.baidu.com:8150/"
13 | },
14 | "online":{
15 | "js_url":"http://demix.baidu.com/PyJs/build/"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/plugins/__init__.py
--------------------------------------------------------------------------------
/plugins/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/plugins/__init__.pyc
--------------------------------------------------------------------------------
/plugins/combo.py:
--------------------------------------------------------------------------------
1 | def main(params):
2 | """
3 | """
4 |
5 | return ('text/html' , params)
6 |
--------------------------------------------------------------------------------
/plugins/combo.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/plugins/combo.pyc
--------------------------------------------------------------------------------
/pyjs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import json
5 | import pprint
6 | import sys
7 | import re
8 | import codecs
9 | import os
10 |
11 |
12 | import pyjs
13 |
14 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + os.sep
15 |
16 |
17 |
18 | if __name__ == '__main__':
19 | args = sys.argv
20 |
21 |
22 | target = None
23 |
24 |
25 | if len(args) >1 and args[1] == 'runserver':
26 | pyjs.localserver.setpath(BASE_DIR)
27 | pyjs.localserver.setBaseDir(BASE_DIR)
28 | pyjs.localserver.run()
29 | else:
30 | if len(args) == 1:
31 | target = 'build'
32 | p = pyjs.parser.Parser(BASE_DIR )
33 | elif len(args) == 2:
34 | target = 'build'
35 | p = pyjs.parser.Parser(BASE_DIR , '*' , args[1])
36 | p.write(target)
37 |
--------------------------------------------------------------------------------
/pyjs/__init__.py:
--------------------------------------------------------------------------------
1 | import parser
2 | import utils
3 | import localserver
4 |
--------------------------------------------------------------------------------
/pyjs/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/pyjs/__init__.pyc
--------------------------------------------------------------------------------
/pyjs/localserver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | import BaseHTTPServer
6 | import SimpleHTTPServer
7 | import socket
8 | import mimetypes
9 | import sys
10 | import os
11 | import re
12 | import codecs
13 | import json
14 | import urllib2
15 | import urllib
16 |
17 | import parser
18 |
19 | PATH = ''
20 | PORT = 8150
21 | baseDir = None
22 | encoding = 'utf-8'
23 |
24 | class PrHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
25 | """
26 | """
27 | def sendResponseWithOutput(self, response, contentType, body):
28 | """
29 | handles both str and unicode types
30 | """
31 | self.send_response(response)
32 | self.send_header("Content-Type", contentType)
33 | self.send_header("Content-Length", len(body))
34 | if response == 301:
35 | self.send_header("Location", body)
36 | self.end_headers()
37 | if response != 301:
38 | self.wfile.write(body)
39 |
40 | def getServerConfig(self , encoding , baseDir):
41 | path = baseDir + 'server.json'
42 | if os.path.exists(path):
43 | f = codecs.open(path , 'r' , encoding)
44 | serverConfig = f.read()
45 | f.close()
46 | return json.loads(serverConfig)
47 |
48 |
49 | def urlProxy(self , url):
50 | response = urllib2.urlopen(url)
51 | contentType = response.info()['Content-Type']
52 | body = response.read()
53 | return (contentType,body)
54 |
55 | def pluginsProxy(self , plugins , params):
56 | try:
57 | exec("from " + plugins+" import main")
58 | return main(params)
59 | except:
60 | return ('text/html;charset=utf-8' , "Unexpected error:"+ str(sys.exc_info()))
61 |
62 |
63 |
64 | def do_GET(self):
65 | """
66 |
67 | Arguments:
68 | - `self`:
69 | """
70 | global encoding
71 | global baseDir
72 | truncate_path = self.path.split('?')[0].split('#')[0]
73 | path_items = self.path.split('?')
74 | response = 200
75 |
76 |
77 | serverConfig = self.getServerConfig(encoding , baseDir)
78 |
79 | isInServerConfig = False
80 | for reg,target in serverConfig.items():
81 | if re.search(reg , truncate_path):
82 | isInServerConfig = True
83 | if target.startswith('http'):#http url
84 | contentType,body =self.urlProxy(target)
85 | elif target.startswith('plugins'):#local plugins
86 | path = ''
87 | if( len(path_items) >1 ):
88 | path = path_items[1]
89 | contentType , body = self.pluginsProxy(target , path)
90 |
91 | if not isInServerConfig:
92 | if truncate_path.endswith('.js'):#可能需要读取包内容
93 | if os.path.exists(baseDir + truncate_path):
94 | response, contentType, body = self.server_static(truncate_path)
95 | else:
96 |
97 | contentType = 'application/javascript; charset='+encoding
98 | package = re.search('\/(\w+?)\.js' , truncate_path)
99 | package = package.group(1)
100 |
101 | p = parser.Parser(baseDir , package , 'local')
102 |
103 | body = p.getFile(package).encode(encoding)
104 | else:
105 | response, contentType, body = self.server_static(truncate_path)
106 | self.sendResponseWithOutput(response , contentType , body)
107 |
108 |
109 |
110 | def server_static(self,file_path):
111 | file_path = '.' + file_path
112 | if not os.path.exists(file_path):
113 | return (404, 'text/html', 'no such file, may be your forget add /doc/, for example "/doc/' + file_path + '"')
114 |
115 | if os.path.isfile(file_path):
116 | stat_result = os.stat(file_path)
117 | mime_type, encoding = mimetypes.guess_type(file_path)
118 |
119 | file = open(file_path, "rb")
120 | try:
121 | return (200, mime_type, file.read())
122 | finally:
123 | file.close()
124 |
125 | elif os.path.isdir(file_path):
126 | if file_path.endswith('/'):
127 | index_file = os.path.join(file_path, 'index.html')
128 | if os.path.exists(index_file):
129 | return (200, 'text/html', open(index_file).read())
130 | else:
131 | return (200 , 'text/html; charset=utf-8' , self.list_directory(os.path.abspath(file_path)).read().encode('utf-8')[150:])
132 | else:
133 | return (301, 'text/html', file_path + '/')
134 | else:
135 | pass
136 |
137 |
138 |
139 |
140 |
141 | def setpath(path):
142 | if os.path.exists(path):
143 | PATH = path
144 |
145 | def setBaseDir(m):
146 | global baseDir
147 | baseDir = m
148 |
149 |
150 | def run(handler_class = PrHandler):
151 | try:
152 | httpd = BaseHTTPServer.HTTPServer((PATH, PORT), handler_class)
153 | print 'server in http://localhost:' + str(PORT)
154 | httpd.serve_forever()
155 | except socket.error:
156 | print 'may be address already in use'
157 | print 'you can try another port by use "python localServer.py xxx"'
158 | sys.exit(1)
159 |
160 |
161 |
162 | if __name__ == '__main__':
163 | if (len(sys.argv) > 1):
164 | PORT = int(sys.argv[1])
165 | run()
166 |
167 |
168 |
--------------------------------------------------------------------------------
/pyjs/localserver.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/pyjs/localserver.pyc
--------------------------------------------------------------------------------
/pyjs/parser.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/pyjs/parser.pyc
--------------------------------------------------------------------------------
/pyjs/utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import shutil
6 |
7 | def rm(target):
8 | """
9 |
10 | Arguments:
11 | - `target`:
12 | """
13 | if os.path.exists(target):
14 | if os.path.isfile(target):
15 | os.remove(target)
16 | elif os.path.isdir(target):
17 | shutil.rmtree(target)
18 |
19 | return True
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/pyjs/utils.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demix/PyJs/a95071c2ef6a3117e85b122ea6e781f0a35d6c8c/pyjs/utils.pyc
--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------
1 | {
2 | "baidu":"http://www.baidu.com",
3 | "combo":"plugins.combo"
4 | }
5 |
--------------------------------------------------------------------------------
/src/core/__init__.js:
--------------------------------------------------------------------------------
1 | __all__ = ['prelude' , 'init']
2 |
3 |
--------------------------------------------------------------------------------
/src/core/init.js:
--------------------------------------------------------------------------------
1 | exports.init = function(){
2 | alert(1);
3 | };
4 |
5 |
6 |
7 |
8 | var name = require('main');
9 | var name2 = require('main');
10 | require('math')
11 |
12 | exports.VERSION = '2.0';
13 |
14 | exports.main = name.two;
15 |
--------------------------------------------------------------------------------
/src/core/prelude.js:
--------------------------------------------------------------------------------
1 | exports.VERSION = '2.0';
2 |
3 |
4 | exports.name = 'PyJsProxy';
5 |
6 | exports.url = '%#js_url#%';
--------------------------------------------------------------------------------
/src/increment.js:
--------------------------------------------------------------------------------
1 | //我们
2 | var add = require('math').add;
3 | exports.increment = function(val) {
4 | return add(val, 1);
5 | };
6 |
--------------------------------------------------------------------------------
/src/main/__init__.js:
--------------------------------------------------------------------------------
1 | __all__ = ['one' , 'two']
--------------------------------------------------------------------------------
/src/main/one.js:
--------------------------------------------------------------------------------
1 | exports.one = '111';
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/two.js:
--------------------------------------------------------------------------------
1 | exports.two = 'twotwotwo.';
--------------------------------------------------------------------------------
/src/math.js:
--------------------------------------------------------------------------------
1 | exports.add = function() {
2 | var sum = 0, i = 0, args = arguments, l = args.length;
3 | while (i < l) {
4 | sum += args[i++];
5 | }
6 | return sum;
7 | };
8 |
--------------------------------------------------------------------------------
/tools/.svn/all-wcprops:
--------------------------------------------------------------------------------
1 | K 25
2 | svn:wc:ra_dav:version-url
3 | V 46
4 | /repos/!svn/ver/78944/doc/zhengxin/proxy/tools
5 | END
6 |
--------------------------------------------------------------------------------
/tools/.svn/entries:
--------------------------------------------------------------------------------
1 | 10
2 |
3 | dir
4 | 79675
5 | http://fe.baidu.com/repos/doc/zhengxin/proxy/tools
6 | http://fe.baidu.com/repos
7 |
8 |
9 |
10 | 2011-06-22T07:12:48.177099Z
11 | 78944
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 01db0846-df26-4232-8c27-33e78907c97d
28 |
29 | jsdoc-toolkit
30 | dir
31 |
32 | closure_linter
33 | dir
34 |
35 |
--------------------------------------------------------------------------------
/tools/closure_linter/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
--------------------------------------------------------------------------------
/tools/closure_linter/checker.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2007 The Closure Linter Authors. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS-IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Core methods for checking JS files for common style guide violations."""
18 |
19 | __author__ = ('robbyw@google.com (Robert Walker)',
20 | 'ajp@google.com (Andy Perelson)')
21 |
22 | import gflags as flags
23 |
24 | from closure_linter import checkerbase
25 | from closure_linter import closurizednamespacesinfo
26 | from closure_linter import ecmametadatapass
27 | from closure_linter import errors
28 | from closure_linter import javascriptlintrules
29 | from closure_linter import javascriptstatetracker
30 | from closure_linter.common import errorprinter
31 | from closure_linter.common import lintrunner
32 |
33 | flags.DEFINE_list('limited_doc_files', ['dummy.js', 'externs.js'],
34 | 'List of files with relaxed documentation checks. Will not '
35 | 'report errors for missing documentation, some missing '
36 | 'descriptions, or methods whose @return tags don\'t have a '
37 | 'matching return statement.')
38 | flags.DEFINE_list('closurized_namespaces', '',
39 | 'Namespace prefixes, used for testing of'
40 | 'goog.provide/require')
41 | flags.DEFINE_list('ignored_extra_namespaces', '',
42 | 'Fully qualified namespaces that should be not be reported '
43 | 'as extra by the linter.')
44 |
45 |
46 | class JavaScriptStyleChecker(checkerbase.CheckerBase):
47 | """Checker that applies JavaScriptLintRules."""
48 |
49 | def __init__(self, error_handler):
50 | """Initialize an JavaScriptStyleChecker object.
51 |
52 | Args:
53 | error_handler: Error handler to pass all errors to
54 | """
55 | self._namespaces_info = None
56 | if flags.FLAGS.closurized_namespaces:
57 | self._namespaces_info = (
58 | closurizednamespacesinfo.ClosurizedNamespacesInfo(
59 | flags.FLAGS.closurized_namespaces,
60 | flags.FLAGS.ignored_extra_namespaces))
61 |
62 | checkerbase.CheckerBase.__init__(
63 | self,
64 | error_handler=error_handler,
65 | lint_rules=javascriptlintrules.JavaScriptLintRules(
66 | self._namespaces_info),
67 | state_tracker=javascriptstatetracker.JavaScriptStateTracker(),
68 | metadata_pass=ecmametadatapass.EcmaMetaDataPass(),
69 | limited_doc_files=flags.FLAGS.limited_doc_files)
70 |
71 | def _CheckTokens(self, token, parse_error, debug_tokens):
72 | """Checks a token stream for lint warnings/errors.
73 |
74 | Adds a separate pass for computing dependency information based on
75 | goog.require and goog.provide statements prior to the main linting pass.
76 |
77 | Args:
78 | token: The first token in the token stream.
79 | parse_error: A ParseError if any errors occurred.
80 | debug_tokens: Whether every token should be printed as it is encountered
81 | during the pass.
82 |
83 | Returns:
84 | A boolean indicating whether the full token stream could be checked or if
85 | checking failed prematurely.
86 | """
87 | # To maximize the amount of errors that get reported before a parse error
88 | # is displayed, don't run the dependency pass if a parse error exists.
89 | if self._namespaces_info and not parse_error:
90 | self._namespaces_info.Reset()
91 | result = (self._ExecutePass(token, self._DependencyPass) and
92 | self._ExecutePass(token, self._LintPass,
93 | debug_tokens=debug_tokens))
94 | else:
95 | result = self._ExecutePass(token, self._LintPass, parse_error,
96 | debug_tokens)
97 |
98 | if not result:
99 | return False
100 |
101 | self._lint_rules.Finalize(self._state_tracker, self._tokenizer.mode)
102 |
103 | self._error_handler.FinishFile()
104 | return True
105 |
106 | def _DependencyPass(self, token):
107 | """Processes an invidual token for dependency information.
108 |
109 | Used to encapsulate the logic needed to process an individual token so that
110 | it can be passed to _ExecutePass.
111 |
112 | Args:
113 | token: The token to process.
114 | """
115 | self._namespaces_info.ProcessToken(token, self._state_tracker)
116 |
117 |
118 | class GJsLintRunner(lintrunner.LintRunner):
119 | """Wrapper class to run GJsLint."""
120 |
121 | def Run(self, filenames, error_handler=None):
122 | """Run GJsLint on the given filenames.
123 |
124 | Args:
125 | filenames: The filenames to check
126 | error_handler: An optional ErrorHandler object, an ErrorPrinter is used if
127 | none is specified.
128 |
129 | Returns:
130 | error_count, file_count: The number of errors and the number of files that
131 | contain errors.
132 | """
133 | if not error_handler:
134 | error_handler = errorprinter.ErrorPrinter(errors.NEW_ERRORS)
135 |
136 | checker = JavaScriptStyleChecker(error_handler)
137 |
138 | # Check the list of files.
139 | for filename in filenames:
140 | checker.Check(filename)
141 |
142 | return error_handler
143 |
--------------------------------------------------------------------------------
/tools/closure_linter/closurizednamespacesinfo_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 The Closure Linter Authors. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS-IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Unit tests for ClosurizedNamespacesInfo."""
18 |
19 |
20 |
21 | import unittest as googletest
22 | from closure_linter import closurizednamespacesinfo
23 | from closure_linter import javascripttokenizer
24 | from closure_linter import javascripttokens
25 |
26 | # Shorthand
27 | Token = javascripttokens.JavaScriptToken
28 | Type = javascripttokens.JavaScriptTokenType
29 |
30 |
31 | class ClosurizedNamespacesInfoTest(googletest.TestCase):
32 | """Tests for ClosurizedNamespacesInfo."""
33 |
34 | __test_cases = {
35 | 'package.CONSTANT': 'package',
36 | 'package.methodName': 'package',
37 | 'package.subpackage.methodName': 'package.subpackage',
38 | 'package.subpackage.methodName.apply': 'package.subpackage',
39 | 'package.ClassName.something': 'package.ClassName',
40 | 'package.ClassName.Enum.VALUE.methodName': 'package.ClassName.Enum',
41 | 'package.ClassName.CONSTANT': 'package.ClassName',
42 | 'package.namespace.CONSTANT.methodName': 'package.namespace',
43 | 'package.ClassName.inherits': 'package.ClassName',
44 | 'package.ClassName.apply': 'package.ClassName',
45 | 'package.ClassName.methodName.apply': 'package.ClassName',
46 | 'package.ClassName.methodName.call': 'package.ClassName',
47 | 'package.ClassName.prototype.methodName': 'package.ClassName',
48 | 'package.ClassName.privateMethod_': 'package.ClassName',
49 | 'package.className.privateProperty_': 'package.className',
50 | 'package.className.privateProperty_.methodName': 'package.className',
51 | 'package.ClassName.PrivateEnum_': 'package.ClassName.PrivateEnum_',
52 | 'package.ClassName.prototype.methodName.apply': 'package.ClassName',
53 | 'package.ClassName.property.subProperty': 'package.ClassName',
54 | 'package.className.prototype.something.somethingElse': 'package.className'
55 | }
56 |
57 | def testGetClosurizedNamespace(self):
58 | """Tests that the correct namespace is returned for various identifiers."""
59 | namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
60 | closurized_namespaces=['package'], ignored_extra_namespaces=[])
61 | for identifier, expected_namespace in self.__test_cases.items():
62 | actual_namespace = namespaces_info.GetClosurizedNamespace(identifier)
63 | self.assertEqual(
64 | expected_namespace,
65 | actual_namespace,
66 | 'expected namespace "' + str(expected_namespace) +
67 | '" for identifier "' + str(identifier) + '" but was "' +
68 | str(actual_namespace) + '"')
69 |
70 | def testIgnoredExtraNamespaces(self):
71 | """Tests that ignored_extra_namespaces are ignored."""
72 | token = self._GetRequireTokens('package.Something')
73 | namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
74 | closurized_namespaces=['package'],
75 | ignored_extra_namespaces=['package.Something'])
76 |
77 | self.assertFalse(namespaces_info.IsExtraRequire(token),
78 | 'Should be valid since it is in ignored namespaces.')
79 |
80 | namespaces_info = closurizednamespacesinfo.ClosurizedNamespacesInfo(
81 | ['package'], [])
82 |
83 | self.assertTrue(namespaces_info.IsExtraRequire(token),
84 | 'Should be invalid since it is not in ignored namespaces.')
85 |
86 | def _GetRequireTokens(self, namespace):
87 | """Returns a list of tokens for a goog.require of the given namespace."""
88 | line_text = 'goog.require(\'' + namespace + '\');\n'
89 | return javascripttokenizer.JavaScriptTokenizer().TokenizeFile([line_text])
90 |
91 | if __name__ == '__main__':
92 | googletest.main()
93 |
94 |
--------------------------------------------------------------------------------
/tools/closure_linter/common/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
--------------------------------------------------------------------------------
/tools/closure_linter/common/error.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2007 The Closure Linter Authors. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS-IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Error object commonly used in linters."""
18 |
19 | __author__ = ('robbyw@google.com (Robert Walker)',
20 | 'ajp@google.com (Andy Perelson)')
21 |
22 |
23 | class Error(object):
24 | """Object representing a style error."""
25 |
26 | def __init__(self, code, message, token, position, fix_data):
27 | """Initialize the error object.
28 |
29 | Args:
30 | code: The numeric error code.
31 | message: The error message string.
32 | token: The tokens.Token where the error occurred.
33 | position: The position of the error within the token.
34 | fix_data: Data to be used in autofixing. Codes with fix_data are:
35 | GOOG_REQUIRES_NOT_ALPHABETIZED - List of string value tokens that are
36 | class names in goog.requires calls.
37 | """
38 | self.code = code
39 | self.message = message
40 | self.token = token
41 | self.position = position
42 | if token:
43 | self.start_index = token.start_index
44 | else:
45 | self.start_index = 0
46 | self.fix_data = fix_data
47 | if self.position:
48 | self.start_index += self.position.start
49 |
50 | def Compare(a, b):
51 | """Compare two error objects, by source code order.
52 |
53 | Args:
54 | a: First error object.
55 | b: Second error object.
56 |
57 | Returns:
58 | A Negative/0/Positive number when a is before/the same as/after b.
59 | """
60 | line_diff = a.token.line_number - b.token.line_number
61 | if line_diff:
62 | return line_diff
63 |
64 | return a.start_index - b.start_index
65 | Compare = staticmethod(Compare)
66 |
--------------------------------------------------------------------------------
/tools/closure_linter/common/erroraccumulator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2008 The Closure Linter Authors. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS-IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Linter error handler class that accumulates an array of errors."""
18 |
19 | __author__ = ('robbyw@google.com (Robert Walker)',
20 | 'ajp@google.com (Andy Perelson)')
21 |
22 |
23 | from closure_linter.common import errorhandler
24 |
25 |
26 | class ErrorAccumulator(errorhandler.ErrorHandler):
27 | """Error handler object that accumulates errors in a list."""
28 |
29 | def __init__(self):
30 | self._errors = []
31 |
32 | def HandleError(self, error):
33 | """Append the error to the list.
34 |
35 | Args:
36 | error: The error object
37 | """
38 | self._errors.append((error.token.line_number, error.code))
39 |
40 | def GetErrors(self):
41 | """Returns the accumulated errors.
42 |
43 | Returns:
44 | A sequence of errors.
45 | """
46 | return self._errors
47 |
--------------------------------------------------------------------------------
/tools/closure_linter/common/errorhandler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2008 The Closure Linter Authors. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS-IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Interface for a linter error handler.
18 |
19 | Error handlers aggregate a set of errors from multiple files and can optionally
20 | perform some action based on the reported errors, for example, logging the error
21 | or automatically fixing it.
22 | """
23 |
24 | __author__ = ('robbyw@google.com (Robert Walker)',
25 | 'ajp@google.com (Andy Perelson)')
26 |
27 |
28 | class ErrorHandler(object):
29 | """Error handler interface."""
30 |
31 | def __init__(self):
32 | if self.__class__ == ErrorHandler:
33 | raise NotImplementedError('class ErrorHandler is abstract')
34 |
35 | def HandleFile(self, filename, first_token):
36 | """Notifies this ErrorHandler that subsequent errors are in filename.
37 |
38 | Args:
39 | filename: The file being linted.
40 | first_token: The first token of the file.
41 | """
42 |
43 | def HandleError(self, error):
44 | """Append the error to the list.
45 |
46 | Args:
47 | error: The error object
48 | """
49 |
50 | def FinishFile(self):
51 | """Finishes handling the current file.
52 |
53 | Should be called after all errors in a file have been handled.
54 | """
55 |
56 | def GetErrors(self):
57 | """Returns the accumulated errors.
58 |
59 | Returns:
60 | A sequence of errors.
61 | """
62 |
--------------------------------------------------------------------------------
/tools/closure_linter/common/filetestcase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2007 The Closure Linter Authors. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS-IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """Test case that runs a checker on a file, matching errors against annotations.
18 |
19 | Runs the given checker on the given file, accumulating all errors. The list
20 | of errors is then matched against those annotated in the file. Based heavily
21 | on devtools/javascript/gpylint/full_test.py.
22 | """
23 |
24 | __author__ = ('robbyw@google.com (Robert Walker)',
25 | 'ajp@google.com (Andy Perelson)')
26 |
27 | import re
28 |
29 | import unittest as googletest
30 | from closure_linter.common import erroraccumulator
31 |
32 |
33 | class AnnotatedFileTestCase(googletest.TestCase):
34 | """Test case to run a linter against a single file."""
35 |
36 | # Matches an all caps letters + underscores error identifer
37 | _MESSAGE = {'msg': '[A-Z][A-Z_]+'}
38 | # Matches a //, followed by an optional line number with a +/-, followed by a
39 | # list of message IDs. Used to extract expected messages from testdata files.
40 | # TODO(robbyw): Generalize to use different commenting patterns.
41 | _EXPECTED_RE = re.compile(r'\s*//\s*(?:(?P