From 04ee83210e2969ba1696684de5c1aec809f40644 Mon Sep 17 00:00:00 2001 From: Akhmedov Farid Date: Thu, 18 Jan 2024 01:43:27 +0700 Subject: [PATCH 1/2] feat: Add support for phpstan's array shapes --- .../vk/kphpstorm/KphpStormParserDefinition.kt | 1 + .../ShapeKeyInvocationCompletionProvider.kt | 13 ++- .../exphptype/ExPhpTypeArrayShape.kt | 46 +++++++++ .../exphptype/PhpTypeToExPhpTypeParsing.kt | 94 +++++++++++++++++++ .../psi/ExPhpTypeArrayShapePsiImpl.kt | 42 +++++++++ .../psi/TokensToExPhpTypePsiParsing.kt | 50 ++++++++++ .../typeProviders/TupleShapeTypeProvider.kt | 5 +- 7 files changed, 245 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt create mode 100644 src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ExPhpTypeArrayShapePsiImpl.kt diff --git a/src/main/kotlin/com/vk/kphpstorm/KphpStormParserDefinition.kt b/src/main/kotlin/com/vk/kphpstorm/KphpStormParserDefinition.kt index eb688433..18b1340b 100644 --- a/src/main/kotlin/com/vk/kphpstorm/KphpStormParserDefinition.kt +++ b/src/main/kotlin/com/vk/kphpstorm/KphpStormParserDefinition.kt @@ -46,6 +46,7 @@ class KphpStormParserDefinition() : PhpParserDefinition() { ExPhpTypeArrayPsiImpl.elementType -> ExPhpTypeArrayPsiImpl(node) ExPhpTypeTuplePsiImpl.elementType -> ExPhpTypeTuplePsiImpl(node) ExPhpTypeShapePsiImpl.elementType -> ExPhpTypeShapePsiImpl(node) + ExPhpTypeArrayShapePsiImpl.elementType -> ExPhpTypeArrayShapePsiImpl(node) ExPhpTypeNullablePsiImpl.elementType -> ExPhpTypeNullablePsiImpl(node) ExPhpTypeTplInstantiationPsiImpl.elementType -> ExPhpTypeTplInstantiationPsiImpl(node) ExPhpTypeCallablePsiImpl.elementType -> ExPhpTypeCallablePsiImpl(node) diff --git a/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt b/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt index 45501ca6..b65c7243 100644 --- a/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt +++ b/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt @@ -12,6 +12,7 @@ import com.jetbrains.php.lang.psi.resolve.types.PhpType import com.vk.kphpstorm.exphptype.ExPhpTypeNullable import com.vk.kphpstorm.exphptype.ExPhpTypePipe import com.vk.kphpstorm.exphptype.ExPhpTypeShape +import com.vk.kphpstorm.exphptype.ExPhpTypeArrayShape import com.vk.kphpstorm.helpers.toExPhpType /** @@ -54,12 +55,16 @@ class ShapeKeyInvocationCompletionProvider : CompletionProvider? { val parsed = type.toExPhpType() val shapeInType = when (parsed) { - is ExPhpTypePipe -> parsed.items.firstOrNull { it is ExPhpTypeShape } + is ExPhpTypePipe -> parsed.items.firstOrNull { it is ExPhpTypeShape } is ExPhpTypeNullable -> parsed.inner - else -> parsed - } as? ExPhpTypeShape ?: return null + else -> parsed + } - return shapeInType.items + return when (shapeInType) { + is ExPhpTypeShape -> shapeInType.items + is ExPhpTypeArrayShape -> shapeInType.items + else -> null + } } } } diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt new file mode 100644 index 00000000..1b582259 --- /dev/null +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt @@ -0,0 +1,46 @@ +package com.vk.kphpstorm.exphptype + +import com.intellij.openapi.project.Project +import com.jetbrains.php.lang.psi.elements.PhpPsiElement +import com.jetbrains.php.lang.psi.resolve.types.PhpType + +/** + * shape(x:int, y:?A, ...) — shape is a list of shape items + * vararg flag is not stored here: does not influence any behavior + */ +// TODO: generalize ShapeItem for both ArrayShape and regular Shape +class ExPhpTypeArrayShape(val items: List) : ExPhpType { + override fun toString() = "array{${items.joinToString(",")}}" + + override fun toHumanReadable(expr: PhpPsiElement) = "array{${items.joinToString { it.toHumanReadable(expr) }}}" + + override fun toPhpType(): PhpType { + val itemsStrJoined = + items.joinToString(",") { "${it.keyName}${if (it.nullable) "?" else ""}:${it.type.toPhpType()}" } + return PhpType().add("array{$itemsStrJoined}") + } + + override fun getSubkeyByIndex(indexKey: String): ExPhpType? { + if (indexKey.isEmpty()) + return ExPhpType.ANY + + return items.find { it.keyName == indexKey }?.type + } + + override fun instantiateTemplate(nameMap: Map): ExPhpType { + return ExPhpTypeArrayShape(items.map { + ExPhpTypeShape.ShapeItem( + it.keyName, + it.nullable, + it.type.instantiateTemplate(nameMap) + ) + }) + } + + override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) { + is ExPhpTypeAny -> true + is ExPhpTypePipe -> rhs.isAssignableTo(this, project) + is ExPhpTypeArrayShape -> true // any array shape is compatible with any other, for simplification (tuples are not) + else -> false + } +} diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt index 62615650..977e8440 100644 --- a/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt @@ -109,6 +109,18 @@ object PhpTypeToExPhpTypeParsing { offset++ } + inline fun rollbackOnNull(operation: () -> T?): T? { + val curr = offset + val result = operation() + + if (result == null) { + offset = curr + return null + } else { + return result + } + } + fun compare(c: Char): Boolean { skipWhitespace() return offset < type.length && type[offset] == c @@ -130,6 +142,45 @@ object PhpTypeToExPhpTypeParsing { offset = match.range.last + 1 return match.value } + + fun parseStringLiteral(): String? { + skipWhitespace() + + return buildString { + if (type.length - offset < 2) return null + + // "..." + // ^ + if (type[offset] == '"' || type[offset] == '\'') { + offset++ + } + + // "..." + // ^ + while (type[offset] != '\'' && type[offset] != '"') { + if (type[offset] == '\\') { + offset++ + when (type[offset]) { + 'n' -> append('\n') + '"' -> append('"') + '\'' -> append('\'') + else -> append('\\') + } + offset++ + } else { + append(type[offset++]) + } + } + + // "..." + // ^ + if (type[offset] != '"' && type[offset] != '\'') { + return null + } + + offset++ + } + } } @@ -265,6 +316,11 @@ object PhpTypeToExPhpTypeParsing { return ExPhpTypeForcing(inner) } + if (fqn == "array" && builder.compare('{')) { + val items = parseArrayShapeContents(builder) ?: return null + return ExPhpTypeArrayShape(items) + } + if (builder.compare('<')) { val specialization = parseTemplateSpecialization(builder) ?: return null return ExPhpTypeTplInstantiation(fqn, specialization) @@ -296,6 +352,44 @@ object PhpTypeToExPhpTypeParsing { return createPipeOrSimplified(pipeItems) } + private fun parseArrayShapeContents(builder: ExPhpTypeBuilder): List? { + if (!builder.compareAndEat('{')) + return null + if (builder.compareAndEat('}')) + return listOf() + + val items = mutableListOf() + + while (true) { + var isString = false // TODO: if it be useful to know where it is string or not, then store it + + val keyName = builder.rollbackOnNull { + builder.parseFQN() + } ?: builder.rollbackOnNull { + isString = true + builder.parseStringLiteral() + } ?: return null + + val nullable = builder.compareAndEat('?') + builder.compareAndEat(':') + val type = parseTypeExpression(builder) ?: return null + + items.add(ExPhpTypeShape.ShapeItem(keyName, nullable, type)) + if (builder.compareAndEat('}')) + return items + + if (builder.compareAndEat(',')) { + if (builder.compareAndEat('.') && builder.compareAndEat('.') && builder.compareAndEat('.')) { + if (!builder.compareAndEat('}')) + return null + return items + } + continue + } + return null + } + } + /** * Having T1|T2|... create ExPhpType representation; not always pipe: int|null will be ?int for example. */ diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ExPhpTypeArrayShapePsiImpl.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ExPhpTypeArrayShapePsiImpl.kt new file mode 100644 index 00000000..1811f8c4 --- /dev/null +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ExPhpTypeArrayShapePsiImpl.kt @@ -0,0 +1,42 @@ +package com.vk.kphpstorm.exphptype.psi + +import com.intellij.lang.ASTNode +import com.intellij.psi.util.elementType +import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes +import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocElementType +import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType +import com.jetbrains.php.lang.documentation.phpdoc.psi.impl.PhpDocTypeImpl +import com.jetbrains.php.lang.psi.resolve.types.PhpType +import com.vk.kphpstorm.helpers.toStringAsNested + +class ExPhpTypeArrayShapePsiImpl(node: ASTNode) : PhpDocTypeImpl(node) { + companion object { + val elementType = PhpDocElementType("exPhpTypeArrayShape") + } + + override fun getNameNode(): ASTNode? = null + + override fun getType(): PhpType { + var itemsStr = "" + var child = firstChild?.nextSibling?.nextSibling // after '(' + while (child != null) { + // key name + if (child.elementType == PhpDocTokenTypes.DOC_IDENTIFIER || child.elementType == PhpDocTokenTypes.DOC_STRING) { + if (itemsStr.length > 1) + itemsStr += ',' + itemsStr += child.text + if (child.nextSibling?.text?.let { it.isNotEmpty() && it[0] == '?' } == true) // nullable + itemsStr += '?' + itemsStr += ':' + } + // key type + if (child is PhpDocType) + itemsStr += child.type.toStringAsNested() + + child = child.nextSibling + } + // vararg shapes with "..." in the end are not reflected in PhpType/ExPhpType + + return PhpType().add("array{$itemsStr}") + } +} diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/TokensToExPhpTypePsiParsing.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/TokensToExPhpTypePsiParsing.kt index e27387e9..6c834578 100644 --- a/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/TokensToExPhpTypePsiParsing.kt +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/TokensToExPhpTypePsiParsing.kt @@ -73,6 +73,45 @@ internal object TokensToExPhpTypePsiParsing { } } + + // example: array{x:int, y?:\A} + private fun parseArrayShapeContents(builder: PhpPsiBuilder): Boolean { + if (!builder.compareAndEat(PhpDocTokenTypes.DOC_LBRACE) && !builder.compareAndEat(PhpDocTokenTypes.DOC_LAB)) + return !builder.expected("{") + + while (true) { + if (!builder.compare(PhpDocTokenTypes.DOC_IDENTIFIER) && !builder.compare(PhpDocTokenTypes.DOC_STRING)) + return builder.expected("key name") + // val keyName = builder.tokenText + builder.advanceLexer() + + val sepOk = builder.compare(PhpDocTokenTypes.DOC_TEXT) && builder.tokenText.let { + it == ":" || it == "?:" + } + + if (sepOk) + builder.advanceLexer() + else + return builder.expected(":") + + if (!parseTypeExpression(builder)) + return builder.expected("expression") + if (builder.compareAndEat(PhpDocTokenTypes.DOC_RBRACE) || builder.compareAndEat(PhpDocTokenTypes.DOC_RAB)) + return true + + if (builder.compareAndEat(PhpDocTokenTypes.DOC_COMMA)) { + if (builder.compare(PhpDocTokenTypes.DOC_TEXT) && builder.tokenText == "...") { + builder.advanceLexer() + if (!builder.compareAndEat(PhpDocTokenTypes.DOC_RBRACE) && !builder.compareAndEat(PhpDocTokenTypes.DOC_RAB)) + return builder.expected("}") + return true + } + continue + } + return builder.expected(", or }") + } + } + private fun parseTemplateSpecialization(builder: PhpPsiBuilder): Boolean { if (!builder.compareAndEat(PhpDocTokenTypes.DOC_LAB)) return !builder.expected("<") @@ -234,6 +273,17 @@ internal object TokensToExPhpTypePsiParsing { return true } + if (builder.compare(PhpDocTokenTypes.DOC_IDENTIFIER) && builder.tokenText == "array" && builder.rawLookup(1) == PhpDocTokenTypes.DOC_LBRACE) { + val marker = builder.mark() + builder.advanceLexer() + if (!parseArrayShapeContents(builder)) { + marker.drop() + return false + } + marker.done(ExPhpTypeArrayShapePsiImpl.elementType) + return true + } + if (builder.compare(PhpDocTokenTypes.DOC_IDENTIFIER) && KphpPrimitiveTypes.mapPrimitiveToPhpType.containsKey(builder.tokenText!!)) { val marker = builder.mark() builder.advanceLexer() diff --git a/src/main/kotlin/com/vk/kphpstorm/typeProviders/TupleShapeTypeProvider.kt b/src/main/kotlin/com/vk/kphpstorm/typeProviders/TupleShapeTypeProvider.kt index 1ceca4f4..40b276e9 100644 --- a/src/main/kotlin/com/vk/kphpstorm/typeProviders/TupleShapeTypeProvider.kt +++ b/src/main/kotlin/com/vk/kphpstorm/typeProviders/TupleShapeTypeProvider.kt @@ -245,10 +245,11 @@ class TupleShapeTypeProvider : PhpTypeProvider4 { private fun inferTypeOfTupleShapeByIndex(wholeType: PhpType, indexKey: String): PhpType? { // optimization: parse wholeType from string only if tuple/shape exist in it val needsCustomIndexing = wholeType.types.any { - it.length > 7 && it[5] == '(' // tuple(, shape(, force( + (it.length > 7 && it[5] == '(' // tuple(, shape(, force( || it == "\\kmixed" // kmixed[*] is kmixed, not PhpStorm 'mixed' meaning uninferred || it == "\\any" // any[*] is any, not undefined - || it == "\\array" // array[*] is any (untyped arrays) + || it == "\\array") // array[*] is any (untyped arrays) + || it.slice(0..5) == "array{" // array{ - phpstan-like array shape, similar to shape( } if (!needsCustomIndexing) return null From 0bbddc44efa0251d67f67b4bceb1927dde418317 Mon Sep 17 00:00:00 2001 From: Akhmedov Farid Date: Mon, 22 Jan 2024 15:14:38 +0700 Subject: [PATCH 2/2] fix: improve array-shape keys suggestion placing --- .../ShapeKeyInvocationCompletionProvider.kt | 6 ++-- .../ShapeKeyUsageCompletionProvider.kt | 1 + .../exphptype/ExPhpTypeArrayShape.kt | 19 +++++++++++-- .../vk/kphpstorm/exphptype/ExPhpTypeShape.kt | 13 +++++---- .../exphptype/PhpTypeToExPhpTypeParsing.kt | 28 +++++++------------ .../kphpstorm/exphptype/psi/ArrayShapeItem.kt | 8 ++++++ 6 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ArrayShapeItem.kt diff --git a/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt b/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt index b65c7243..c3127e9d 100644 --- a/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt +++ b/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyInvocationCompletionProvider.kt @@ -13,6 +13,7 @@ import com.vk.kphpstorm.exphptype.ExPhpTypeNullable import com.vk.kphpstorm.exphptype.ExPhpTypePipe import com.vk.kphpstorm.exphptype.ExPhpTypeShape import com.vk.kphpstorm.exphptype.ExPhpTypeArrayShape +import com.vk.kphpstorm.exphptype.psi.ArrayShapeItem import com.vk.kphpstorm.helpers.toExPhpType /** @@ -52,9 +53,8 @@ class ShapeKeyInvocationCompletionProvider : CompletionProvider? { - val parsed = type.toExPhpType() - val shapeInType = when (parsed) { + fun detectPossibleKeysOfShape(type: PhpType): List? { + val shapeInType = when (val parsed = type.toExPhpType()) { is ExPhpTypePipe -> parsed.items.firstOrNull { it is ExPhpTypeShape } is ExPhpTypeNullable -> parsed.inner else -> parsed diff --git a/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyUsageCompletionProvider.kt b/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyUsageCompletionProvider.kt index f1346f54..c84cad04 100644 --- a/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyUsageCompletionProvider.kt +++ b/src/main/kotlin/com/vk/kphpstorm/completion/ShapeKeyUsageCompletionProvider.kt @@ -16,6 +16,7 @@ class ShapeKeyUsageCompletionProvider : CompletionProvider val shapeItems = ShapeKeyInvocationCompletionProvider.detectPossibleKeysOfShape(lhs.type) ?: return for (item in shapeItems) + // TODO: if completion has escape sequences, we need to change single quotes to double quotes result.addElement(LookupElementBuilder.create(item.keyName).withTypeText(item.type.toString()).withInsertHandler(ArrayKeyInsertHandler)) // PhpStorm also tries to suggest keys based on usage (not on type, of course) diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt index 1b582259..ac689ac1 100644 --- a/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArrayShape.kt @@ -3,13 +3,25 @@ package com.vk.kphpstorm.exphptype import com.intellij.openapi.project.Project import com.jetbrains.php.lang.psi.elements.PhpPsiElement import com.jetbrains.php.lang.psi.resolve.types.PhpType +import com.vk.kphpstorm.exphptype.psi.ArrayShapeItem /** * shape(x:int, y:?A, ...) — shape is a list of shape items * vararg flag is not stored here: does not influence any behavior */ -// TODO: generalize ShapeItem for both ArrayShape and regular Shape -class ExPhpTypeArrayShape(val items: List) : ExPhpType { +class ExPhpTypeArrayShape(val items: List) : ExPhpType { + class ShapeItem( + override val keyName: String, + val isString: Boolean, + val nullable: Boolean, + override val type: ExPhpType + ) : + ArrayShapeItem { + override fun toString() = "$keyName${if (nullable) "?" else ""}:$type" + fun toHumanReadable(file: PhpPsiElement) = + "${if (isString) "\"$keyName\"" else keyName}${if (nullable) "?" else ""}:${type.toHumanReadable(file)}" + } + override fun toString() = "array{${items.joinToString(",")}}" override fun toHumanReadable(expr: PhpPsiElement) = "array{${items.joinToString { it.toHumanReadable(expr) }}}" @@ -29,8 +41,9 @@ class ExPhpTypeArrayShape(val items: List) : ExPhpType override fun instantiateTemplate(nameMap: Map): ExPhpType { return ExPhpTypeArrayShape(items.map { - ExPhpTypeShape.ShapeItem( + ShapeItem( it.keyName, + it.isString, it.nullable, it.type.instantiateTemplate(nameMap) ) diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeShape.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeShape.kt index 2b6efcf5..0f923588 100644 --- a/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeShape.kt +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeShape.kt @@ -3,13 +3,15 @@ package com.vk.kphpstorm.exphptype import com.intellij.openapi.project.Project import com.jetbrains.php.lang.psi.elements.PhpPsiElement import com.jetbrains.php.lang.psi.resolve.types.PhpType +import com.vk.kphpstorm.exphptype.psi.ArrayShapeItem /** * shape(x:int, y:?A, ...) — shape is a list of shape items * vararg flag is not stored here: does not influence any behavior */ class ExPhpTypeShape(val items: List) : ExPhpType { - class ShapeItem(val keyName: String, val nullable: Boolean, val type: ExPhpType) { + class ShapeItem(override val keyName: String, val nullable: Boolean, override val type: ExPhpType) : + ArrayShapeItem { override fun toString() = "$keyName${if (nullable) "?" else ""}:$type" fun toHumanReadable(file: PhpPsiElement) = "$keyName${if (nullable) "?" else ""}:${type.toHumanReadable(file)}" } @@ -19,7 +21,8 @@ class ExPhpTypeShape(val items: List) : ExPhpType { override fun toHumanReadable(expr: PhpPsiElement) = "shape(${items.joinToString { it.toHumanReadable(expr) }})" override fun toPhpType(): PhpType { - val itemsStrJoined = items.joinToString(",") { "${it.keyName}${if (it.nullable) "?" else ""}:${it.type.toPhpType()}" } + val itemsStrJoined = + items.joinToString(",") { "${it.keyName}${if (it.nullable) "?" else ""}:${it.type.toPhpType()}" } return PhpType().add("shape($itemsStrJoined)") } @@ -34,9 +37,9 @@ class ExPhpTypeShape(val items: List) : ExPhpType { } override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) { - is ExPhpTypeAny -> true - is ExPhpTypePipe -> rhs.isAssignableTo(this, project) + is ExPhpTypeAny -> true + is ExPhpTypePipe -> rhs.isAssignableTo(this, project) is ExPhpTypeShape -> true // any shape is compatible with any other, for simplification (tuples are not) - else -> false + else -> false } } diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt index 977e8440..1d321afa 100644 --- a/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/PhpTypeToExPhpTypeParsing.kt @@ -151,25 +151,17 @@ object PhpTypeToExPhpTypeParsing { // "..." // ^ - if (type[offset] == '"' || type[offset] == '\'') { - offset++ + when (type[offset]) { + '\'' -> offset++ + '\"' -> offset++ + else -> return null } + // "..." // ^ while (type[offset] != '\'' && type[offset] != '"') { - if (type[offset] == '\\') { - offset++ - when (type[offset]) { - 'n' -> append('\n') - '"' -> append('"') - '\'' -> append('\'') - else -> append('\\') - } - offset++ - } else { - append(type[offset++]) - } + append(type[offset++]) } // "..." @@ -352,16 +344,16 @@ object PhpTypeToExPhpTypeParsing { return createPipeOrSimplified(pipeItems) } - private fun parseArrayShapeContents(builder: ExPhpTypeBuilder): List? { + private fun parseArrayShapeContents(builder: ExPhpTypeBuilder): List? { if (!builder.compareAndEat('{')) return null if (builder.compareAndEat('}')) return listOf() - val items = mutableListOf() + val items = mutableListOf() while (true) { - var isString = false // TODO: if it be useful to know where it is string or not, then store it + var isString = false val keyName = builder.rollbackOnNull { builder.parseFQN() @@ -374,7 +366,7 @@ object PhpTypeToExPhpTypeParsing { builder.compareAndEat(':') val type = parseTypeExpression(builder) ?: return null - items.add(ExPhpTypeShape.ShapeItem(keyName, nullable, type)) + items.add(ExPhpTypeArrayShape.ShapeItem(keyName, isString, nullable, type)) if (builder.compareAndEat('}')) return items diff --git a/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ArrayShapeItem.kt b/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ArrayShapeItem.kt new file mode 100644 index 00000000..eb697c21 --- /dev/null +++ b/src/main/kotlin/com/vk/kphpstorm/exphptype/psi/ArrayShapeItem.kt @@ -0,0 +1,8 @@ +package com.vk.kphpstorm.exphptype.psi + +import com.vk.kphpstorm.exphptype.ExPhpType + +interface ArrayShapeItem { + val keyName: String + val type: ExPhpType +} \ No newline at end of file