diff --git a/zpa-core/src/main/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitor.kt b/zpa-core/src/main/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitor.kt index 1cc0e6a6..4043cd44 100644 --- a/zpa-core/src/main/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitor.kt +++ b/zpa-core/src/main/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitor.kt @@ -306,12 +306,23 @@ class SymbolVisitor(private val typeSolver: DefaultTypeSolver, private val globa private fun visitAssociativeArrayDeclaration(node: AstNode) { val identifier = node.getFirstChild(PlSqlGrammar.IDENTIFIER_NAME) - createSymbol(identifier, Symbol.Kind.TYPE, AssociativeArrayDatatype(node)) + val datatype = node.getFirstChild(PlSqlGrammar.NESTED_TABLE_DEFINITION).getFirstChild(PlSqlGrammar.DATATYPE) + createSymbol(identifier, Symbol.Kind.TYPE, AssociativeArrayDatatype(node, currentScope, solveType(datatype))) } private fun visitRecordDeclaration(node: AstNode) { val identifier = node.getFirstChild(PlSqlGrammar.IDENTIFIER_NAME) - createSymbol(identifier, Symbol.Kind.TYPE, RecordDatatype()) + + val scope = currentScope ?: throw IllegalStateException("Cannot create a symbol without a scope.") + + val fields = node.getChildren(PlSqlGrammar.RECORD_FIELD_DECLARATION).map { field -> + val fieldName = field.getFirstChild(PlSqlGrammar.IDENTIFIER_NAME) + val datatype = field.getFirstChild(PlSqlGrammar.DATATYPE) + val type = solveType(datatype) + Symbol(fieldName, Symbol.Kind.VARIABLE, scope, type) + } + + createSymbol(identifier, Symbol.Kind.TYPE, RecordDatatype(node, currentScope, fields)) } private fun visitParameterDeclaration(node: AstNode) { diff --git a/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/AssociativeArrayDatatype.kt b/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/AssociativeArrayDatatype.kt index ffe09173..b0abd5f2 100644 --- a/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/AssociativeArrayDatatype.kt +++ b/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/AssociativeArrayDatatype.kt @@ -22,12 +22,16 @@ package org.sonar.plugins.plsqlopen.api.symbols.datatype import com.felipebz.flr.api.AstNode import org.sonar.plugins.plsqlopen.api.PlSqlGrammar import org.sonar.plugins.plsqlopen.api.symbols.PlSqlType +import org.sonar.plugins.plsqlopen.api.symbols.Scope -class AssociativeArrayDatatype(node: AstNode? = null) : PlSqlDatatype { +class AssociativeArrayDatatype(node: AstNode? = null, currentScope: Scope?, val nestedType: PlSqlDatatype) : PlSqlDatatype { override val type = PlSqlType.ASSOCIATIVE_ARRAY - val nestedType = node?.getFirstChild(PlSqlGrammar.NESTED_TABLE_DEFINITION)?.getFirstChild(PlSqlGrammar.DATATYPE)?.tokenOriginalValue - ?: throw IllegalStateException("Associative array must have a nested type") + val name: String = currentScope?.let { + if (it.identifier != null && it.type == PlSqlGrammar.CREATE_PACKAGE) + it.identifier + "." + else "" } + + node?.getFirstChild(PlSqlGrammar.IDENTIFIER_NAME)?.tokenOriginalValue override fun toString(): String { return "AssociativeArray{nestedType=$nestedType}" diff --git a/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/RecordDatatype.kt b/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/RecordDatatype.kt index 20e27221..e2d14641 100644 --- a/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/RecordDatatype.kt +++ b/zpa-core/src/main/kotlin/org/sonar/plugins/plsqlopen/api/symbols/datatype/RecordDatatype.kt @@ -19,11 +19,21 @@ */ package org.sonar.plugins.plsqlopen.api.symbols.datatype +import com.felipebz.flr.api.AstNode +import org.sonar.plugins.plsqlopen.api.PlSqlGrammar import org.sonar.plugins.plsqlopen.api.symbols.PlSqlType +import org.sonar.plugins.plsqlopen.api.symbols.Scope +import org.sonar.plugins.plsqlopen.api.symbols.Symbol -class RecordDatatype : PlSqlDatatype { +class RecordDatatype(node: AstNode? = null, currentScope: Scope?, val fields: List) : PlSqlDatatype { override val type = PlSqlType.RECORD + val name: String = currentScope?.let { + if (it.identifier != null && it.type == PlSqlGrammar.CREATE_PACKAGE) + it.identifier + "." + else "" } + + node?.getFirstChild(PlSqlGrammar.IDENTIFIER_NAME)?.tokenOriginalValue + override fun toString(): String { return "Record" } diff --git a/zpa-core/src/test/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitorTest.kt b/zpa-core/src/test/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitorTest.kt index cbc2bd3c..274b59dc 100644 --- a/zpa-core/src/test/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitorTest.kt +++ b/zpa-core/src/test/kotlin/org/sonar/plsqlopen/symbols/SymbolVisitorTest.kt @@ -27,6 +27,7 @@ import org.sonar.plsqlopen.TestPlSqlVisitorRunner import org.sonar.plugins.plsqlopen.api.symbols.PlSqlType import org.sonar.plugins.plsqlopen.api.symbols.Symbol import org.sonar.plugins.plsqlopen.api.symbols.datatype.NumericDatatype +import org.sonar.plugins.plsqlopen.api.symbols.datatype.RecordDatatype import java.io.File class SymbolVisitorTest { @@ -116,11 +117,50 @@ end; assertThat(type.type).isEqualTo(PlSqlType.RECORD) assertThat(type.innerScope).isNull() + val datatype = type.datatype as RecordDatatype + assertThat(datatype.name).isEqualTo("my_record"); + val variable = symbols.find("variable", 3, 3) assertThat(variable.type).isEqualTo(PlSqlType.RECORD) assertThat(variable.references).isEmpty() } + @Test + fun recordInProcedure() { + val symbols = scan(""" +create or replace procedure my_proc is + type my_record is record (x number); +begin + null; +end; +""") + assertThat(symbols).hasSize(2) // TODO + + val type = symbols.find("my_record", 2, 8) + assertThat(type.type).isEqualTo(PlSqlType.RECORD) + assertThat(type.innerScope).isNull() + + val datatype = type.datatype as RecordDatatype + assertThat(datatype.name).isEqualTo("my_record"); + } + + @Test + fun recordInPackage() { + val symbols = scan(""" +create or replace package my_pack is + type my_record is record (x number); +end; +""") + assertThat(symbols).hasSize(2) // TODO + + val type = symbols.find("my_record", 2, 8) + assertThat(type.type).isEqualTo(PlSqlType.RECORD) + assertThat(type.innerScope).isNull() + + val datatype = type.datatype as RecordDatatype + assertThat(datatype.name).isEqualTo("my_pack.my_record"); + } + @Test fun forLoop() { val symbols = scan("""