Skip to content

Commit

Permalink
暂时采用一种性能略耗的方案
Browse files Browse the repository at this point in the history
  • Loading branch information
CppCXY committed Apr 15, 2022
1 parent ce0faf2 commit 30bc80e
Show file tree
Hide file tree
Showing 10 changed files with 455 additions and 292 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ class LuaTextDocumentService(private val workspace: LuaWorkspaceService) : TextD
val position = arr[1].toInt()
file.psi?.findElementAt(position)?.let { psi ->
PsiTreeUtil.getParentOfType(psi, LuaClassMember::class.java)?.let { member ->
val doc = documentProvider.generateDoc(member, member)
val doc = documentProvider.generateDoc(member, false)
val content = MarkupContent()
content.kind = "markdown"
content.value = doc
Expand All @@ -378,7 +378,7 @@ class LuaTextDocumentService(private val workspace: LuaWorkspaceService) : TextD
val element = TargetElementUtil.findTarget(file.psi, pos)
if (element != null) {
val ref = element.reference?.resolve() ?: element
val doc = documentProvider.generateDoc(ref, element)
val doc = documentProvider.generateDoc(ref, true)
if (doc != null)
hover = Hover(listOf(Either.forLeft(doc)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ object DiagnosticsOptions {
var undeclaredVariable = InspectionsLevel.None

var assignValidation = InspectionsLevel.None

var deprecated = InspectionsLevel.Warning
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.tang.intellij.lua.search.SearchContext
import com.tang.intellij.lua.ty.*
import com.tang.lsp.ILuaFile
import com.tang.lsp.toRange
import com.tang.vscode.diagnostics.inspections.*
import org.eclipse.lsp4j.Diagnostic
import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.DiagnosticTag
Expand Down Expand Up @@ -37,292 +38,24 @@ object DiagnosticsService {
}
}
is LuaIndexExpr -> {
// indexDeprecatedInspections(it, file, diagnostics)
fieldValidationInspections(it, file, diagnostics)
DeprecatedInspection.indexDeprecatedInspections(it, file, diagnostics)
FieldValidInspection.fieldValidationInspections(it, file, diagnostics)
}
is LuaCallExpr -> {
// callDeprecatedInspections(it, file, diagnostics)
callExprInspections(it, file, diagnostics)
FunctionInspection.callExprInspections(it, file, diagnostics)
}
is LuaAssignStat -> {
assignInspections(it, file, diagnostics)
AssignInspection.assignInspections(it, file, diagnostics)
}
is LuaNameExpr -> {
undeclaredVariableInspections(it, file, diagnostics)
DeprecatedInspection.nameExprDeprecatedInspections(it, file, diagnostics)
UndeclaredVariableInspection.undeclaredVariableInspections(it, file, diagnostics)
}
}

true
}
}

private fun callDeprecatedInspections(o: LuaCallExpr, file: ILuaFile, diagnostics: MutableList<Diagnostic>) {
val expr = o.expr
if (expr is LuaNameExpr) {
val resolve = expr.reference?.resolve()
if (resolve is LuaFuncDef && resolve.isDeprecated) {
val diagnostic = Diagnostic()
diagnostic.message = "deprecated"
diagnostic.severity = DiagnosticSeverity.Hint
diagnostic.tags = listOf(DiagnosticTag.Deprecated)
diagnostic.range = expr.textRange.toRange(file)
diagnostics.add(diagnostic)
}
}
}

private fun indexDeprecatedInspections(o: LuaIndexExpr, file: ILuaFile, diagnostics: MutableList<Diagnostic>) {
val searchContext = SearchContext.get(o.project)
val res = resolve(o, searchContext)
if ((res is LuaClassMethodDef && res.isDeprecated)
|| (res is LuaClassField && res.isDeprecated)
) {
o.id?.let { id ->
val diagnostic = Diagnostic()
diagnostic.message = "deprecated"
diagnostic.severity = DiagnosticSeverity.Hint
diagnostic.tags = listOf(DiagnosticTag.Deprecated)
diagnostic.range = id.textRange.toRange(file)
diagnostics.add(diagnostic)
}
}
}

private fun fieldValidationInspections(o: LuaIndexExpr, file: ILuaFile, diagnostics: MutableList<Diagnostic>) {
if (DiagnosticsOptions.fieldValidation != InspectionsLevel.None) {
if (o.parent is LuaVarList) {
return
}
val searchContext = SearchContext.get(o.project)
val res = resolve(o, searchContext)
val context = SearchContext.get(o.project)
val prefixType = o.guessParentType(context)

if (prefixType !is TyUnknown && res == null) {
o.id?.let { id ->
val diagnostic = Diagnostic()
diagnostic.message = "Undefined property '${id.text}'"
diagnostic.severity = makeSeverity(DiagnosticsOptions.fieldValidation)
diagnostic.range = id.textRange.toRange(file)
diagnostics.add(diagnostic)
}
}
}
}

private fun assignInspections(o: LuaAssignStat, file: ILuaFile, diagnostics: MutableList<Diagnostic>) {
if (DiagnosticsOptions.assignValidation != InspectionsLevel.None) {
val assignees = o.varExprList.exprList
val values = o.valueExprList?.exprList ?: listOf()
val searchContext = SearchContext.get(o.project)

// Check right number of fields/assignments
if (assignees.size > values.size) {
for (i in values.size until assignees.size) {
val diagnostic = Diagnostic()
diagnostic.message = "Missing value assignment."
diagnostic.severity = makeSeverity(DiagnosticsOptions.assignValidation)
diagnostic.range = assignees[i].textRange.toRange(file)
diagnostics.add(diagnostic)
}
} else if (assignees.size < values.size) {
for (i in assignees.size until values.size) {
val diagnostic = Diagnostic()
diagnostic.message = "Nothing to assign to."
diagnostic.severity = makeSeverity(DiagnosticsOptions.assignValidation)
diagnostic.range = values[i].textRange.toRange(file)
diagnostics.add(diagnostic)
}
} else {
// Try to match types for each assignment
for (i in 0 until assignees.size) {
val field = assignees[i]
val name = field.name ?: ""
val value = values[i]
val valueType = value.guessType(searchContext)

// Field access
if (field is LuaIndexExpr) {
// Get owner class
val parent = field.guessParentType(searchContext)

if (parent is TyClass) {
val fieldType = parent.findMemberType(name, searchContext) ?: Ty.NIL

if (!valueType.subTypeOf(fieldType, searchContext, false)) {
val diagnostic = Diagnostic()
diagnostic.message =
"Type mismatch. Required: '%s' Found: '%s'".format(fieldType, valueType)
diagnostic.severity = makeSeverity(DiagnosticsOptions.assignValidation)
diagnostic.range = value.textRange.toRange(file)
diagnostics.add(diagnostic)
}
}
} else {
// Local/global var assignments, only check type if there is no comment defining it
if (o.comment == null) {
val fieldType = field.guessType(searchContext)
if (!valueType.subTypeOf(fieldType, searchContext, false)) {
val diagnostic = Diagnostic()
diagnostic.message =
"Type mismatch. Required: '%s' Found: '%s'".format(fieldType, valueType)
diagnostic.severity = makeSeverity(DiagnosticsOptions.assignValidation)
diagnostic.range = value.textRange.toRange(file)
diagnostics.add(diagnostic)
}
}
}
}
}
}
}


private fun callExprInspections(callExpr: LuaCallExpr, file: ILuaFile, diagnostics: MutableList<Diagnostic>) {
if (DiagnosticsOptions.parameterValidation != InspectionsLevel.None) {
var nCommas = 0
val paramMap = mutableMapOf<Int, LuaTypeGuessable>()
callExpr.args.firstChild?.let { firstChild ->
var child: PsiElement? = firstChild
while (child != null) {
if (child.node.elementType == LuaTypes.COMMA) {
nCommas++
} else {
if (child is LuaTypeGuessable) {
paramMap[nCommas] = child
}
}

child = child.nextSibling
}
}
val context = SearchContext.get(callExpr.project)
callExpr.guessParentType(context).let { parentType ->
parentType.each { ty ->
if (ty is ITyFunction) {
val sig = ty.findPerfectSignature(nCommas + 1)

var index = 0;

var skipFirstParam = false

if (sig.colonCall && callExpr.isMethodDotCall) {
index++;
} else if (!sig.colonCall && callExpr.isMethodColonCall) {
skipFirstParam = true
}

sig.params.forEach { pi ->
if (skipFirstParam) {
skipFirstParam = false
return@forEach
}

val param = paramMap[index]
if (param != null) {
val paramType = param.guessType(context)
if (!paramTypeCheck(pi, param, context)) {
val diagnostic = Diagnostic()
diagnostic.message =
"Type mismatch '${paramType.displayName}' not match type '${pi.ty.displayName}'"
diagnostic.severity = makeSeverity(DiagnosticsOptions.parameterValidation)
diagnostic.range = param.textRange.toRange(file)
diagnostics.add(diagnostic)
}
} else if (!pi.nullable) {
val diagnostic = Diagnostic()
diagnostic.message =
"Too few arguments to function call"
diagnostic.severity = makeSeverity(DiagnosticsOptions.parameterValidation)
val endOffset = callExpr.textRange.endOffset
diagnostic.range = TextRange(endOffset, endOffset).toRange(file)
diagnostics.add(diagnostic)
return@each
}
++index;
}
//可变参数暂时不做验证
}
}
}
}
}

private fun undeclaredVariableInspections(o: LuaNameExpr, file: ILuaFile, diagnostics: MutableList<Diagnostic>) {
if (DiagnosticsOptions.undeclaredVariable != InspectionsLevel.None) {
val res = resolve(o, SearchContext.get(o.project))

if (res == null) {
val diagnostic = Diagnostic()
diagnostic.message = "Undeclared variable '%s'.".format(o.text)
diagnostic.severity = makeSeverity(DiagnosticsOptions.undeclaredVariable)
diagnostic.range = o.textRange.toRange(file)
diagnostics.add(diagnostic)

}
}
}

private fun makeSeverity(level: InspectionsLevel): DiagnosticSeverity {
return when (level) {
InspectionsLevel.None -> DiagnosticSeverity.Information
InspectionsLevel.Warning -> DiagnosticSeverity.Warning
InspectionsLevel.Error -> DiagnosticSeverity.Error
}
}

private fun paramTypeCheck(param: LuaParamInfo, variable: LuaTypeGuessable, context: SearchContext): Boolean {
val variableType = variable.guessType(context)
val defineType = param.ty

if (DiagnosticsOptions.defineTypeCanReceiveNilType && variableType.kind == TyKind.Nil) {
return true
}

if (!param.nullable && variableType.kind == TyKind.Nil) {
return false
}

// 由于没有接口 interface
// 那么将匿名表传递给具有特定类型的定义类型也都被认为是合理的
// 暂时不做field检查
if (variable is LuaTableExpr &&
(defineType.kind == TyKind.Class || defineType.kind == TyKind.Array || defineType.kind == TyKind.Tuple)
) {
return true
}

// 类似于回调函数的写法,不写传参是非常普遍的,所以只需要认为定义类型是个函数就通过
if (variable is LuaClosureExpr && defineType.kind == TyKind.Function) {
return true
}

return typeCheck(defineType, variableType, context)
}

private fun typeCheck(defineType: ITy, variableType: ITy, context: SearchContext): Boolean {
if (DiagnosticsOptions.anyTypeCanAssignToAnyDefineType && variableType is TyUnknown) {
return true
}

if (DiagnosticsOptions.defineAnyTypeCanBeAssignedByAnyVariable && defineType is TyUnknown) {
return true
}

if (defineType is TyUnion) {
var isUnionCheckPass = false
defineType.each {
if (typeCheck(it, variableType, context)) {
isUnionCheckPass = true
return@each
}
}

if (isUnionCheckPass) {
return true
}
}

return variableType.subTypeOf(defineType, context, true)
}
}
Loading

0 comments on commit 30bc80e

Please sign in to comment.