@@ -10,6 +10,7 @@ import com.tang.intellij.lua.search.SearchContext
1010import com.tang.intellij.lua.ty.*
1111import com.tang.lsp.ILuaFile
1212import com.tang.lsp.toRange
13+ import com.tang.vscode.diagnostics.inspections.*
1314import org.eclipse.lsp4j.Diagnostic
1415import org.eclipse.lsp4j.DiagnosticSeverity
1516import org.eclipse.lsp4j.DiagnosticTag
@@ -37,292 +38,24 @@ object DiagnosticsService {
3738 }
3839 }
3940 is LuaIndexExpr -> {
40- // indexDeprecatedInspections(it, file, diagnostics)
41- fieldValidationInspections(it, file, diagnostics)
41+ DeprecatedInspection . indexDeprecatedInspections(it, file, diagnostics)
42+ FieldValidInspection . fieldValidationInspections(it, file, diagnostics)
4243 }
4344 is LuaCallExpr -> {
44- // callDeprecatedInspections(it, file, diagnostics)
45- callExprInspections(it, file, diagnostics)
45+ FunctionInspection .callExprInspections(it, file, diagnostics)
4646 }
4747 is LuaAssignStat -> {
48- assignInspections(it, file, diagnostics)
48+ AssignInspection . assignInspections(it, file, diagnostics)
4949 }
5050 is LuaNameExpr -> {
51- undeclaredVariableInspections(it, file, diagnostics)
51+ DeprecatedInspection .nameExprDeprecatedInspections(it, file, diagnostics)
52+ UndeclaredVariableInspection .undeclaredVariableInspections(it, file, diagnostics)
5253 }
5354 }
54-
5555 true
5656 }
5757 }
5858
59- private fun callDeprecatedInspections (o : LuaCallExpr , file : ILuaFile , diagnostics : MutableList <Diagnostic >) {
60- val expr = o.expr
61- if (expr is LuaNameExpr ) {
62- val resolve = expr.reference?.resolve()
63- if (resolve is LuaFuncDef && resolve.isDeprecated) {
64- val diagnostic = Diagnostic ()
65- diagnostic.message = " deprecated"
66- diagnostic.severity = DiagnosticSeverity .Hint
67- diagnostic.tags = listOf (DiagnosticTag .Deprecated )
68- diagnostic.range = expr.textRange.toRange(file)
69- diagnostics.add(diagnostic)
70- }
71- }
72- }
73-
74- private fun indexDeprecatedInspections (o : LuaIndexExpr , file : ILuaFile , diagnostics : MutableList <Diagnostic >) {
75- val searchContext = SearchContext .get(o.project)
76- val res = resolve(o, searchContext)
77- if ((res is LuaClassMethodDef && res.isDeprecated)
78- || (res is LuaClassField && res.isDeprecated)
79- ) {
80- o.id?.let { id ->
81- val diagnostic = Diagnostic ()
82- diagnostic.message = " deprecated"
83- diagnostic.severity = DiagnosticSeverity .Hint
84- diagnostic.tags = listOf (DiagnosticTag .Deprecated )
85- diagnostic.range = id.textRange.toRange(file)
86- diagnostics.add(diagnostic)
87- }
88- }
89- }
90-
91- private fun fieldValidationInspections (o : LuaIndexExpr , file : ILuaFile , diagnostics : MutableList <Diagnostic >) {
92- if (DiagnosticsOptions .fieldValidation != InspectionsLevel .None ) {
93- if (o.parent is LuaVarList ) {
94- return
95- }
96- val searchContext = SearchContext .get(o.project)
97- val res = resolve(o, searchContext)
98- val context = SearchContext .get(o.project)
99- val prefixType = o.guessParentType(context)
100-
101- if (prefixType !is TyUnknown && res == null ) {
102- o.id?.let { id ->
103- val diagnostic = Diagnostic ()
104- diagnostic.message = " Undefined property '${id.text} '"
105- diagnostic.severity = makeSeverity(DiagnosticsOptions .fieldValidation)
106- diagnostic.range = id.textRange.toRange(file)
107- diagnostics.add(diagnostic)
108- }
109- }
110- }
111- }
112-
113- private fun assignInspections (o : LuaAssignStat , file : ILuaFile , diagnostics : MutableList <Diagnostic >) {
114- if (DiagnosticsOptions .assignValidation != InspectionsLevel .None ) {
115- val assignees = o.varExprList.exprList
116- val values = o.valueExprList?.exprList ? : listOf ()
117- val searchContext = SearchContext .get(o.project)
118-
119- // Check right number of fields/assignments
120- if (assignees.size > values.size) {
121- for (i in values.size until assignees.size) {
122- val diagnostic = Diagnostic ()
123- diagnostic.message = " Missing value assignment."
124- diagnostic.severity = makeSeverity(DiagnosticsOptions .assignValidation)
125- diagnostic.range = assignees[i].textRange.toRange(file)
126- diagnostics.add(diagnostic)
127- }
128- } else if (assignees.size < values.size) {
129- for (i in assignees.size until values.size) {
130- val diagnostic = Diagnostic ()
131- diagnostic.message = " Nothing to assign to."
132- diagnostic.severity = makeSeverity(DiagnosticsOptions .assignValidation)
133- diagnostic.range = values[i].textRange.toRange(file)
134- diagnostics.add(diagnostic)
135- }
136- } else {
137- // Try to match types for each assignment
138- for (i in 0 until assignees.size) {
139- val field = assignees[i]
140- val name = field.name ? : " "
141- val value = values[i]
142- val valueType = value.guessType(searchContext)
143-
144- // Field access
145- if (field is LuaIndexExpr ) {
146- // Get owner class
147- val parent = field.guessParentType(searchContext)
148-
149- if (parent is TyClass ) {
150- val fieldType = parent.findMemberType(name, searchContext) ? : Ty .NIL
151-
152- if (! valueType.subTypeOf(fieldType, searchContext, false )) {
153- val diagnostic = Diagnostic ()
154- diagnostic.message =
155- " Type mismatch. Required: '%s' Found: '%s'" .format(fieldType, valueType)
156- diagnostic.severity = makeSeverity(DiagnosticsOptions .assignValidation)
157- diagnostic.range = value.textRange.toRange(file)
158- diagnostics.add(diagnostic)
159- }
160- }
161- } else {
162- // Local/global var assignments, only check type if there is no comment defining it
163- if (o.comment == null ) {
164- val fieldType = field.guessType(searchContext)
165- if (! valueType.subTypeOf(fieldType, searchContext, false )) {
166- val diagnostic = Diagnostic ()
167- diagnostic.message =
168- " Type mismatch. Required: '%s' Found: '%s'" .format(fieldType, valueType)
169- diagnostic.severity = makeSeverity(DiagnosticsOptions .assignValidation)
170- diagnostic.range = value.textRange.toRange(file)
171- diagnostics.add(diagnostic)
172- }
173- }
174- }
175- }
176- }
177- }
178- }
179-
180-
181- private fun callExprInspections (callExpr : LuaCallExpr , file : ILuaFile , diagnostics : MutableList <Diagnostic >) {
182- if (DiagnosticsOptions .parameterValidation != InspectionsLevel .None ) {
183- var nCommas = 0
184- val paramMap = mutableMapOf<Int , LuaTypeGuessable >()
185- callExpr.args.firstChild?.let { firstChild ->
186- var child: PsiElement ? = firstChild
187- while (child != null ) {
188- if (child.node.elementType == LuaTypes .COMMA ) {
189- nCommas++
190- } else {
191- if (child is LuaTypeGuessable ) {
192- paramMap[nCommas] = child
193- }
194- }
195-
196- child = child.nextSibling
197- }
198- }
199- val context = SearchContext .get(callExpr.project)
200- callExpr.guessParentType(context).let { parentType ->
201- parentType.each { ty ->
202- if (ty is ITyFunction ) {
203- val sig = ty.findPerfectSignature(nCommas + 1 )
204-
205- var index = 0 ;
20659
207- var skipFirstParam = false
20860
209- if (sig.colonCall && callExpr.isMethodDotCall) {
210- index++ ;
211- } else if (! sig.colonCall && callExpr.isMethodColonCall) {
212- skipFirstParam = true
213- }
214-
215- sig.params.forEach { pi ->
216- if (skipFirstParam) {
217- skipFirstParam = false
218- return @forEach
219- }
220-
221- val param = paramMap[index]
222- if (param != null ) {
223- val paramType = param.guessType(context)
224- if (! paramTypeCheck(pi, param, context)) {
225- val diagnostic = Diagnostic ()
226- diagnostic.message =
227- " Type mismatch '${paramType.displayName} ' not match type '${pi.ty.displayName} '"
228- diagnostic.severity = makeSeverity(DiagnosticsOptions .parameterValidation)
229- diagnostic.range = param.textRange.toRange(file)
230- diagnostics.add(diagnostic)
231- }
232- } else if (! pi.nullable) {
233- val diagnostic = Diagnostic ()
234- diagnostic.message =
235- " Too few arguments to function call"
236- diagnostic.severity = makeSeverity(DiagnosticsOptions .parameterValidation)
237- val endOffset = callExpr.textRange.endOffset
238- diagnostic.range = TextRange (endOffset, endOffset).toRange(file)
239- diagnostics.add(diagnostic)
240- return @each
241- }
242- ++ index;
243- }
244- // 可变参数暂时不做验证
245- }
246- }
247- }
248- }
249- }
250-
251- private fun undeclaredVariableInspections (o : LuaNameExpr , file : ILuaFile , diagnostics : MutableList <Diagnostic >) {
252- if (DiagnosticsOptions .undeclaredVariable != InspectionsLevel .None ) {
253- val res = resolve(o, SearchContext .get(o.project))
254-
255- if (res == null ) {
256- val diagnostic = Diagnostic ()
257- diagnostic.message = " Undeclared variable '%s'." .format(o.text)
258- diagnostic.severity = makeSeverity(DiagnosticsOptions .undeclaredVariable)
259- diagnostic.range = o.textRange.toRange(file)
260- diagnostics.add(diagnostic)
261-
262- }
263- }
264- }
265-
266- private fun makeSeverity (level : InspectionsLevel ): DiagnosticSeverity {
267- return when (level) {
268- InspectionsLevel .None -> DiagnosticSeverity .Information
269- InspectionsLevel .Warning -> DiagnosticSeverity .Warning
270- InspectionsLevel .Error -> DiagnosticSeverity .Error
271- }
272- }
273-
274- private fun paramTypeCheck (param : LuaParamInfo , variable : LuaTypeGuessable , context : SearchContext ): Boolean {
275- val variableType = variable.guessType(context)
276- val defineType = param.ty
277-
278- if (DiagnosticsOptions .defineTypeCanReceiveNilType && variableType.kind == TyKind .Nil ) {
279- return true
280- }
281-
282- if (! param.nullable && variableType.kind == TyKind .Nil ) {
283- return false
284- }
285-
286- // 由于没有接口 interface
287- // 那么将匿名表传递给具有特定类型的定义类型也都被认为是合理的
288- // 暂时不做field检查
289- if (variable is LuaTableExpr &&
290- (defineType.kind == TyKind .Class || defineType.kind == TyKind .Array || defineType.kind == TyKind .Tuple )
291- ) {
292- return true
293- }
294-
295- // 类似于回调函数的写法,不写传参是非常普遍的,所以只需要认为定义类型是个函数就通过
296- if (variable is LuaClosureExpr && defineType.kind == TyKind .Function ) {
297- return true
298- }
299-
300- return typeCheck(defineType, variableType, context)
301- }
302-
303- private fun typeCheck (defineType : ITy , variableType : ITy , context : SearchContext ): Boolean {
304- if (DiagnosticsOptions .anyTypeCanAssignToAnyDefineType && variableType is TyUnknown ) {
305- return true
306- }
307-
308- if (DiagnosticsOptions .defineAnyTypeCanBeAssignedByAnyVariable && defineType is TyUnknown ) {
309- return true
310- }
311-
312- if (defineType is TyUnion ) {
313- var isUnionCheckPass = false
314- defineType.each {
315- if (typeCheck(it, variableType, context)) {
316- isUnionCheckPass = true
317- return @each
318- }
319- }
320-
321- if (isUnionCheckPass) {
322- return true
323- }
324- }
325-
326- return variableType.subTypeOf(defineType, context, true )
327- }
32861}
0 commit comments