parsejs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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