1
+ # -----------------------------------------------------------------------
2
+ # This file is part of MoonScript
3
+ #
4
+ # MoonSript is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # MoonSript is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with MoonSript. If not, see <https://www.gnu.org/licenses/>.
16
+ #
17
+ # Copyright (C) 2025 Krisna Pranav, MoonScript Developers
18
+ # -----------------------------------------------------------------------
19
+
20
+ module MoonScript
21
+ class TypeChecker
22
+ def check_all (node : Ast ::Component ) : Checkable
23
+ resolve node
24
+
25
+ resolve node.constants
26
+ resolve node.functions
27
+ resolve node.gets
28
+
29
+ VOID
30
+ end
31
+
32
+ def check (node : Ast ::Component ) : Checkable
33
+ check_global_names node.name.value, node
34
+
35
+ checked =
36
+ {} of String => Ast ::Node
37
+
38
+ check_names(node.properties, " component" , checked)
39
+ check_names(node.functions, " component" , checked)
40
+ check_names(node.states, " component" , checked)
41
+ check_names(node.gets, " component" , checked)
42
+
43
+ if node.name.value == " Main" && (property = node.properties.first?)
44
+ error! :component_main_properties do
45
+ block do
46
+ text " The"
47
+ bold " Main"
48
+ text " component cannot have properties."
49
+ end
50
+
51
+ snippet " A property is already defined here:" , property
52
+ snippet " The component in qa:" , node
53
+ end
54
+ end
55
+
56
+ node.refs.reduce({} of String => Ast ::Node ) do |memo , (variable , ref )|
57
+ name = variable.value
58
+ other = memo[name]?
59
+
60
+ error! :component_reference_name_conflict do
61
+ snippet " There are multiple references with the name:" , name
62
+ snippet " references:" , variable
63
+ snippet " references:" , other
64
+ end if other
65
+
66
+ memo[name] = variable
67
+ memo
68
+ end
69
+
70
+ node.styles.reduce({} of String => Ast ::Node ) do |memo , style |
71
+ name = style.name.value
72
+ other = memo[name]?
73
+
74
+ error! :component_style_name_conflict do
75
+ snippet " There are multiple styles with the name:" , name
76
+ snippet " references:" , style
77
+ snippet " references:" , other
78
+ end if other
79
+
80
+ memo[name] = style
81
+ memo
82
+ end
83
+
84
+ node.connects.each do |connect |
85
+ other = (node.connects - [connect]).find(& .store.value.== (connect.store.value))
86
+
87
+ return error! :component_multiple_stores do
88
+ block do
89
+ text " The component is connected to the store"
90
+ bold %( "#{ connect.store.value } ")
91
+ text " multiple times."
92
+ end
93
+
94
+ snippet " references:" , other
95
+ snippet " references:" , connect
96
+ end if other
97
+ end
98
+
99
+ node.connects.each do |connect |
100
+ connect.keys.each do |key |
101
+ variable = key.target || key.name
102
+ other = checked[variable.value]?
103
+
104
+ error! :component_exposed_name_conflict do
105
+ block do
106
+ text " You cannot expose"
107
+ bold %( "#{ variable.value } ")
108
+ text " from the store because the name is already taken."
109
+ end
110
+
111
+ snippet " The entity with the same name is here:" , other
112
+ snippet " the expose is here:" , key
113
+ end if other
114
+ end
115
+ end
116
+
117
+ node.connects.reduce({} of String => Ast ::Node ) do |memo , connect |
118
+ connect.keys.each do |key |
119
+ variable = key.target || key.name
120
+ other = memo[variable.value]?
121
+
122
+ error! :component_multiple_exposed do
123
+ block do
124
+ text " The entity"
125
+ bold %( "#{ variable.value } ")
126
+ text " from a store is exposed multiple times."
127
+ end
128
+
129
+ snippet " It is exposed here:" , other
130
+ snippet " " , key
131
+ end if other
132
+
133
+ memo[variable.value] = key
134
+ end
135
+ memo
136
+ end
137
+
138
+ node.uses.each do |use |
139
+ other = (node.uses - [use]).find(& .provider.value.== (use.provider.value))
140
+
141
+ error! :component_multiple_providers do
142
+ block do
143
+ text " You are subcribing to the provider"
144
+ bold %( "#{ other.provider.value } ")
145
+ text " in a component multiple times."
146
+ end
147
+
148
+ snippet " A subscription is here:" , other
149
+ snippet " subscription:" , use
150
+ end if other
151
+ end
152
+
153
+ resolve node.properties
154
+ resolve node.connects
155
+ resolve node.states
156
+ resolve node.uses
157
+
158
+ error! :component_no_render_function do
159
+ block do
160
+ text " A component must have a"
161
+ bold " render function"
162
+ text " This component does not have one:"
163
+ end
164
+
165
+ snippet node
166
+ end unless node.functions.any?(& .name.value.== (" render" ))
167
+
168
+ node.functions.each do |function |
169
+ case function.name.value
170
+ when " render"
171
+ type =
172
+ resolve function
173
+
174
+ matches =
175
+ [HTML , STRING , HTML_CHILDREN , TEXT_CHILDREN ].any? do |item |
176
+ Comparer .compare(type , Type .new(" Function" , [item] of Checkable ))
177
+ end
178
+
179
+ error! :component_render_function_mismatch do
180
+ block do
181
+ text " expecting the type of the"
182
+ bold " render"
183
+ text " function to match one of types:"
184
+ end
185
+
186
+ snippet <<-PLAIN
187
+ Function(Array(String))
188
+ Function(Array(Html))
189
+ Function(String)
190
+ Function(Html)
191
+ PLAIN
192
+
193
+ snippet " Instead the type of the function is:" , type .parameters.first
194
+ snippet " function in question:" , function
195
+ end unless matches
196
+ when " componentDidMount" ,
197
+ " componentDidUpdate" ,
198
+ " componentWillUnmount"
199
+ type =
200
+ resolve function
201
+
202
+ error! :component_lifecycle_function_mismatch do
203
+ block do
204
+ text " The type of the function"
205
+ bold %( "#{ function.name.value } ")
206
+ text " of a component must be:"
207
+ end
208
+
209
+ snippet VOID_FUNCTION
210
+ snippet " Instead it is:" , type
211
+ snippet " function in question:" , function
212
+ end unless Comparer .compare(type , VOID_FUNCTION )
213
+ end
214
+ end
215
+
216
+ VOID
217
+ end
218
+ end
219
+ end
0 commit comments