parsejs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 41b39d4f01ad6648dbb147d8cf758bb0b5cd45ff
4
+ data.tar.gz: 3877eb40098d127ed7a2a036f2ceee54983e2c6b
5
+ SHA512:
6
+ metadata.gz: e4fe5a4c22dea6bb05393054ea312a3eedf38bc76b083b24a73cc0b23736889fb9ca2a8019dd7e8a5cfd0cdd2cbd90d8a87264086746d7bc68f3bd16d13bcd1f
7
+ data.tar.gz: e56c48ca152263de721598472e4b03ac2145455193dd38e4d37dcee4fd66f79a402963e1cd90ec843142050e2e5d4731b937a477f7b972ad78a02fad7d910a49
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbx
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ bin
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -cfs
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby
7
+ - rbx-18mode
8
+ - rbx-19mode
9
+ script: rake spec
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem "pry"
@@ -0,0 +1,85 @@
1
+ # ParseJS
2
+
3
+ ParseJS is a JavaScript parser written in Ruby using the KPeg parser generator. It also
4
+ takes a JavaScript parse tree and emits semantically identical JavaScript. The parser is
5
+ tested by parsing large, popular JavaScript libraries (such as jQuery) and confirming that
6
+ the minified output after a round-trip through ParseJS is the same as minifying the original
7
+ source.
8
+
9
+ The ParseJS stringifier does not guarantee equivalent whitespace and comments in a
10
+ round-trip, but it should guarantee semantic equivalence.
11
+
12
+ The ParseJS parser maintains comments in most cases where comments would represent inline
13
+ documentation.
14
+
15
+ * top-level statement: a ParseJS top-level statement node contains any comment that
16
+ immediately preceded it.
17
+ * property: in a property list (object literal), a property node contains any comment
18
+ that immediately preceded it.
19
+
20
+ There is a work-in-progress AST walker that associates comments with particular structures.
21
+ The ultimate goal of this walker is to identify JavaScript structures that represent
22
+ "classes" or similar structures and associate their comments with information extracted
23
+ from the code.
24
+
25
+ # Usage
26
+
27
+ ParseJS is provided as a Rubygem. At the moment, you can use it in your Gemfile by using
28
+ Bundler's git feature.
29
+
30
+ ```ruby
31
+ gem "parsejs", :git => "git://github.com/wycats/parsejs.git"
32
+ ```
33
+
34
+ You can parse a String of JavaScript and receive an AST by using
35
+ `ParseJS.parse`.
36
+
37
+ ```ruby
38
+ ast = ParseJS.parse(some_data)
39
+ ```
40
+
41
+ You can convert the AST back into a JavaScript String using the
42
+ stringifier. You can mutate the AST before converting it into a String
43
+ if you wish.
44
+
45
+ ```
46
+ ParseJS::Stringifier.to_string(ast)
47
+ ```
48
+
49
+ You can write your own AST walker without implementing visitors for all
50
+ nodes by subclassing `ParseJS::Visitor`. Take a look
51
+ [at that class](https://github.com/wycats/parsejs/blob/master/lib/parsejs/visitor.rb)
52
+ to see the default visitor behavior for a particular node.
53
+
54
+ By default, nodes `accept` their children. Nodes that have children that
55
+ are `Array`s of nodes (e.g. `FunctionDeclaration`, which has an `Array` of
56
+ parameters and an `Array` of statements as children) default to looping
57
+ over the `Array`s and accepting their members.
58
+
59
+ You can take a look at the in-progress
60
+ [docs extractor](https://github.com/wycats/parsejs/blob/master/lib/parsejs/docs.rb)
61
+ or the [stringifier](https://github.com/wycats/parsejs/blob/master/lib/parsejs/stringifier.rb)
62
+ for examples of subclasses of `ParseJS::Visitor`.
63
+
64
+ # LICENSE
65
+
66
+ Copyright (C) 2012 Yehuda Katz
67
+
68
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
69
+ this software and associated documentation files (the "Software"), to deal in
70
+ the Software without restriction, including without limitation the rights to
71
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
72
+ of the Software, and to permit persons to whom the Software is furnished to do
73
+ so, subject to the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be included in all
76
+ copies or substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
79
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
80
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
81
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
82
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
83
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
84
+ SOFTWARE.
85
+
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "bundler/setup"
4
+
5
+ file "lib/parsejs/grammar.kpeg.rb" => "lib/parsejs/grammar.kpeg" do
6
+ sh "kpeg -f lib/parsejs/grammar.kpeg --stand-alone --debug"
7
+ end
8
+
9
+ task :compile => "lib/parsejs/grammar.kpeg.rb"
10
+
11
+ task :spec => :compile do
12
+ sh "rspec -cfs spec"
13
+ end
14
+
15
+ task :default => :compile
@@ -0,0 +1,14 @@
1
+ require "parsejs/version"
2
+ require "parsejs/grammar.kpeg"
3
+ require "parsejs/stringifier"
4
+ require "parsejs/ast"
5
+ require "parsejs/scope"
6
+
7
+ module ParseJS
8
+ def self.parse(string)
9
+ string = string.force_encoding('BINARY') if string.respond_to?(:force_encoding)
10
+ parser = ParseJS::Parser.new(string)
11
+ parser.parse
12
+ parser.result
13
+ end
14
+ end
@@ -0,0 +1,84 @@
1
+ require "parsejs/scope"
2
+
3
+ module ParseJS
4
+ module AST
5
+ class Node
6
+ def type?(string)
7
+ self.class.name.split("::").last == string
8
+ end
9
+
10
+ def cuddly?
11
+ false
12
+ end
13
+
14
+ def statement?
15
+ false
16
+ end
17
+
18
+ def needs_newline?
19
+ false
20
+ end
21
+ end
22
+
23
+ class Comment
24
+ def multiline?
25
+ @type == 'multiline'
26
+ end
27
+ end
28
+
29
+ class SequenceExpression
30
+ attr_accessor :parens
31
+ end
32
+
33
+ class BlockStatement
34
+ attr_accessor :cuddly
35
+
36
+ def cuddle!
37
+ @cuddly = true
38
+ end
39
+
40
+ def needs_newline?
41
+ !@cuddly
42
+ end
43
+
44
+ def cuddly?
45
+ true
46
+ end
47
+
48
+ def statement?
49
+ true
50
+ end
51
+ end
52
+
53
+ class FunctionExpression
54
+ include ParseJS::AST::Scope
55
+ end
56
+
57
+ class FunctionDeclaration
58
+ include ParseJS::AST::Scope
59
+ end
60
+
61
+ class Program
62
+ include ParseJS::AST::Scope
63
+ end
64
+
65
+ statements = [VariableDeclaration, EmptyStatement, ExpressionStatement, IfStatement]
66
+ statements += [WhileStatement, ForStatement, ForInStatement, DoWhileStatement]
67
+ statements += [ContinueStatement, BreakStatement, ReturnStatement, WithStatement]
68
+ statements += [LabeledStatement, SwitchStatement, ThrowStatement, TryStatement]
69
+ statements += [DebuggerStatement, FunctionDeclaration]
70
+
71
+ statements.each do |statement|
72
+ statement.class_eval do
73
+ def needs_newline?
74
+ true
75
+ end
76
+
77
+ def statement?
78
+ true
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
@@ -0,0 +1,237 @@
1
+ require "parsejs/visitor"
2
+ require "yard"
3
+
4
+ module ParseJS
5
+ class CommentScanner < Visitor
6
+ include ParseJS::AST
7
+
8
+ def initialize(*)
9
+ @current_comment = nil
10
+ super
11
+ end
12
+
13
+ def comment?
14
+ return false unless @current_comment
15
+ return false if @current_comment.empty?
16
+ return @current_comment.any?(&:multiline?)
17
+ end
18
+
19
+ def stringify(node)
20
+ Stringifier.to_string(Marshal.load(Marshal.dump(node)))
21
+ end
22
+
23
+ def visit_AssignmentExpression(expr)
24
+ process_comment(expr)
25
+ super
26
+ end
27
+
28
+ def visit_CommentedStatement(comment)
29
+ @current_comment = comment.comments
30
+
31
+ super
32
+ end
33
+
34
+ def visit_Property(property)
35
+ @current_comment = property.comments
36
+ process_comment(property)
37
+
38
+ super
39
+ end
40
+
41
+ def process_comment(node)
42
+ print_comment(node)
43
+ end
44
+
45
+ def print_comment(node)
46
+ return unless comment?
47
+
48
+ puts
49
+ puts "-" * 10 + node.class.name + "-" * 10
50
+ puts @current_comment.map { |c| next unless c.multiline?; c.body }.join("\n")
51
+ puts "-" * (10 + node.class.name.size + 10)
52
+ puts
53
+
54
+ puts Stringifier.to_string(Marshal.load(Marshal.dump(node)))
55
+ puts
56
+ ensure
57
+ @current_comment = nil
58
+ end
59
+
60
+ def strip_leading_whitespace(string)
61
+ min = string.scan(/^[ \t]*(?=\S)/).min
62
+ size = min ? min.size : 0
63
+ string.gsub(/^[ \t]{#{size}}/, '')
64
+ end
65
+ end
66
+
67
+ class ExtractDocs < CommentScanner
68
+ include AST
69
+
70
+ def initialize(*)
71
+ @current_variables = []
72
+ @current_class = []
73
+ end
74
+
75
+ def visit_Program(program)
76
+ with_variables(program, []) { super }
77
+ end
78
+
79
+ def visit_FunctionDeclaration(decl)
80
+ with_variables(decl, decl.params.list.map(&:val)) { super }
81
+ end
82
+
83
+ def visit_FunctionExpression(expr)
84
+ with_variables(expr, expr.params.list.map(&:val)) { super }
85
+ end
86
+
87
+ def with_variables(expr, params=expr.params.map(&:val))
88
+ locals = FindVars.find_variables(expr)
89
+ @current_variables.push(locals | params)
90
+ yield
91
+ ensure
92
+ @current_variables.pop
93
+ end
94
+
95
+ def variable?(name)
96
+ @current_variables.find do |list|
97
+ list.include?(name)
98
+ end
99
+ end
100
+
101
+ def member_left(expr)
102
+ object = expr
103
+
104
+ while object
105
+ if object.is_a?(Identifier)
106
+ return object.val
107
+ elsif object.is_a?(ThisExpression)
108
+ return "this"
109
+ elsif object.respond_to?(:object)
110
+ object = object.object
111
+ else
112
+ return nil
113
+ end
114
+ end
115
+ end
116
+
117
+ def visit_AssignmentExpression(expr)
118
+ right = expr.right
119
+ in_class = false
120
+
121
+ if right.is_a?(CallExpression)
122
+ callee = expr.right.callee
123
+ if callee.is_a?(MemberExpression) && !callee.computed
124
+ property = callee.property.val
125
+ if property == "extend"
126
+ if expr.left.is_a?(MemberExpression)
127
+ left_id = member_left(expr.left)
128
+ extends = stringify(expr.right.callee).sub(/\.extend$/, '')
129
+
130
+ if variable?(left_id)
131
+ # found private class
132
+ else
133
+ klass = stringify(expr.left)
134
+
135
+ left_string = stringify(expr.left)
136
+ namespace_obj = build_namespace left_string.split(".")[0...-1].join("::")
137
+
138
+ class_name = left_string.split(".")[-1]
139
+
140
+ obj = YARD::CodeObjects::ClassObject.new(namespace_obj, class_name)
141
+ obj.docstring = stripped_comment
142
+
143
+ @current_class.push [klass, extends, obj]
144
+ in_class = true
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ super
152
+
153
+ ensure
154
+ @current_class.pop if in_class
155
+ end
156
+
157
+ def build_namespace(namespace)
158
+ if namespace.empty?
159
+ YARD::Registry.root
160
+ elsif ns = YARD::Registry.at(namespace)
161
+ ns
162
+ else
163
+ name = namespace.gsub('::', '.')
164
+ YARD::CodeObjects::NamespaceObject.new(:root, name)
165
+ end
166
+ end
167
+
168
+ def current_class_name
169
+ klass, parent = @current_class.last
170
+ "#{klass} < #{parent}"
171
+ end
172
+
173
+ def current_yard_class
174
+ @current_class.last[2]
175
+ end
176
+
177
+ def visit_Property(property)
178
+ return if @current_class.empty?
179
+
180
+ @current_comment = property.comments
181
+ return if stripped_comment.empty?
182
+
183
+ case property.value
184
+ when FunctionDeclaration, FunctionExpression
185
+ obj = YARD::CodeObjects::MethodObject.new(current_yard_class, property.key.val)
186
+ obj.docstring = stripped_comment
187
+ else
188
+ # found a non-method property
189
+ end
190
+
191
+ super
192
+ end
193
+
194
+ def stripped_comment
195
+ comments = @current_comment.map do |c|
196
+ next unless c.multiline?
197
+ c.body
198
+ end
199
+
200
+ string = comments.join("\n")
201
+ string = string.gsub(/\A\**\n/, '')
202
+ strip_leading_whitespace(string)
203
+ end
204
+
205
+ def process_comment(*)
206
+ end
207
+ end
208
+
209
+ require "set"
210
+
211
+ class FindVars < Visitor
212
+ def self.find_variables(ast)
213
+ ast = Marshal.load(Marshal.dump(ast))
214
+
215
+ finder = new
216
+ finder.accept(ast)
217
+ finder.variables
218
+ end
219
+
220
+ attr_reader :variables
221
+
222
+ def initialize(*)
223
+ @variables = Set.new
224
+ super
225
+ end
226
+
227
+ def visit_VariableDeclarator(expr)
228
+ @variables << expr.id.val
229
+ end
230
+
231
+ def visit_FunctionDeclaration(*)
232
+ end
233
+
234
+ def visit_FunctionExpression(*)
235
+ end
236
+ end
237
+ end