diff --git a/.gitignore b/.gitignore index b523d384..6fd493e9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ framework-tests/.packed framework-tests/.test-projects .magent eslint-output.txt + +.idea/ +*.iml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f4164..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index dc3e940f..00000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c..00000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549e..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9b8317cd..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/varlock.iml b/.idea/varlock.iml deleted file mode 100644 index c956989b..00000000 --- a/.idea/varlock.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/packages/intellij-plugin/README.md b/packages/intellij-plugin/README.md index 1dd69a3a..036176c3 100644 --- a/packages/intellij-plugin/README.md +++ b/packages/intellij-plugin/README.md @@ -71,7 +71,7 @@ Inspired by the [VS Code / Open VSX extension](../../vscode-plugin): ### Troubleshooting -**Build fails with "What went wrong: 25"** — You're using Java 25, which Gradle 8.x doesn't support. Use Java 17: +**Build fails with "What went wrong: 25"** — You're using Java 25, which isn't supported by the Gradle/toolchain used for this plugin. Use Java 17: ```bash # Homebrew (openjdk@17) diff --git a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecCompletionContributor.kt b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecCompletionContributor.kt index 9aa8de25..086fe788 100644 --- a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecCompletionContributor.kt +++ b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecCompletionContributor.kt @@ -26,7 +26,7 @@ class EnvSpecCompletionContributor : CompletionContributor() { } companion object { - private val ENV_KEY_PATTERN = Pattern.compile("^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*=") + private val ENV_KEY_PATTERN = Pattern.compile("^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\\s*=") private val KOTLIN_ESCAPED_DOLLAR_RE = Regex("\\$\\{'\\$'\\}") private val CHOICE_SNIPPET_RE = Regex("""\$\{\d+\|([^}]*)\|\}""") private val DEFAULT_SNIPPET_RE = Regex("""\$\{\d+:([^}]*)\}""") @@ -34,6 +34,10 @@ class EnvSpecCompletionContributor : CompletionContributor() { private val SIMPLE_TABSTOP_SNIPPET_RE = Regex("""\$\d+""") } + private fun getDecoratorCommentPrefix(linePrefix: String): String? { + return EnvSpecDiagnosticsCore.getDecoratorCommentText(linePrefix) + } + private fun doAddCompletions(params: CompletionParameters, result: CompletionResultSet) { val file = params.originalFile as? EnvSpecFile ?: return val document = params.editor.document @@ -41,8 +45,7 @@ class EnvSpecCompletionContributor : CompletionContributor() { val line = document.getLineNumber(offset) val lineStart = document.getLineStartOffset(line) val linePrefix = document.getText(com.intellij.openapi.util.TextRange(lineStart, offset)) - val commentStart = linePrefix.indexOf('#') - val commentPrefix = if (commentStart >= 0) linePrefix.substring(commentStart + 1) else "" + val commentPrefix = getDecoratorCommentPrefix(linePrefix) val lineDocument = object : LineDocument { override val lineCount: Int get() = document.lineCount @@ -63,7 +66,7 @@ class EnvSpecCompletionContributor : CompletionContributor() { return } - if (commentStart >= 0) { + if (commentPrefix != null) { val existingDecoratorNames = EnvSpecCompletionCore.getExistingDecoratorNames(lineDocument, line, commentPrefix) // Type option completion diff --git a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecDiagnosticsCore.kt b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecDiagnosticsCore.kt index 5c15428b..1a3e7e3c 100644 --- a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecDiagnosticsCore.kt +++ b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecDiagnosticsCore.kt @@ -4,7 +4,10 @@ import java.net.InetAddress object EnvSpecDiagnosticsCore { + private val DECORATOR_LINE_PATTERN = Regex("""^\s*#\s*(@.*)$""") private val DECORATOR_PATTERN = Regex("@([A-Za-z][\\w-]*)(?:\\([^)]*\\)|=[^\\s#]+)?") + private val NON_DECORATOR_LABEL_RE = Regex("^@[A-Za-z]+:") + private val NON_DECORATOR_PROSE_RE = Regex("^@[A-Za-z]+\\s+[^@#]") private const val MAX_MATCHES_PATTERN_LENGTH = 200 private val INCOMPATIBLE_DECORATOR_PAIRS = listOf( listOf("required", "optional"), @@ -15,6 +18,20 @@ object EnvSpecDiagnosticsCore { data class DecoratorOccurrence(val name: String, val line: Int, val start: Int, val end: Int) data class CoreDiagnostic(val line: Int, val start: Int, val end: Int, val message: String) + fun getDecoratorCommentText(lineText: String): String? { + return getDecoratorCommentRange(lineText)?.first + } + + private fun getDecoratorCommentRange(lineText: String): Pair? { + val match = DECORATOR_LINE_PATTERN.find(lineText) ?: return null + val commentText = match.groupValues[1] + if (NON_DECORATOR_LABEL_RE.containsMatchIn(commentText)) return null + if (NON_DECORATOR_PROSE_RE.containsMatchIn(commentText)) return null + val stripped = stripInlineComment(commentText) + val startIndex = match.groups[1]!!.range.first + return stripped to startIndex + } + fun stripInlineComment(value: String): String { var quote: Char? = null for (i in value.indices) { @@ -107,7 +124,8 @@ object EnvSpecDiagnosticsCore { fun getTypeInfoFromPrecedingComments(document: LineDocument, lineNumber: Int): TypeInfo? { val commentBlock = getPrecedingCommentBlock(document, lineNumber) for (index in commentBlock.indices.reversed()) { - val match = Regex("@type=([A-Za-z][\\w-]*)(?:\\((.*)\\))?").find(commentBlock[index]) ?: continue + val decoratorText = getDecoratorCommentText(commentBlock[index]) ?: continue + val match = Regex("@type=([A-Za-z][\\w-]*)(?:\\((.*)\\))?").find(decoratorText) ?: continue return if (match.groupValues[1] == "enum") { TypeInfo("enum", splitEnumArgs(match.groupValues[2]), emptyMap()) } else { @@ -118,8 +136,10 @@ object EnvSpecDiagnosticsCore { } fun getDecoratorOccurrences(lineText: String, lineNumber: Int): List { - return DECORATOR_PATTERN.findAll(lineText).map { match -> - DecoratorOccurrence(match.groupValues[1], lineNumber, match.range.first, match.range.last + 1) + val (decoratorText, startIndex) = getDecoratorCommentRange(lineText) ?: return emptyList() + return DECORATOR_PATTERN.findAll(decoratorText).map { match -> + val absStart = startIndex + (match.range.first) + DecoratorOccurrence(match.groupValues[1], lineNumber, absStart, absStart + match.value.length) }.toList() } diff --git a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecInspection.kt b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecInspection.kt index 93f20994..6267858a 100644 --- a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecInspection.kt +++ b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecInspection.kt @@ -69,6 +69,6 @@ class EnvSpecInspection : LocalInspectionTool() { } companion object { - private val ENV_ASSIGNMENT = Pattern.compile("^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*?)\\s*$") + private val ENV_ASSIGNMENT = Pattern.compile("^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\\s*=\\s*(.*?)\\s*$") } } diff --git a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecLexer.kt b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecLexer.kt index 23e0c5fc..45a66a9e 100644 --- a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecLexer.kt +++ b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecLexer.kt @@ -226,13 +226,13 @@ class EnvSpecLexer : LexerBase() { queue.add(QueuedToken(start + i, start + valueEnd, EnvSpecTokenTypes.DECORATOR_VALUE)) i = valueEnd if (i < text.length && text[i] == '(') { - queue.add(QueuedToken(start + i, start + i + 1, EnvSpecTokenTypes.EQUALS)) + queue.add(QueuedToken(start + i, start + i + 1, EnvSpecTokenTypes.PAREN_OPEN)) val close = findClosingParen(text, i) if (close > i + 1) { enqueueDecoratorArgsTokens(start + i + 1, text.substring(i + 1, close)) } if (close < text.length) { - queue.add(QueuedToken(start + close, start + close + 1, EnvSpecTokenTypes.EQUALS)) + queue.add(QueuedToken(start + close, start + close + 1, EnvSpecTokenTypes.PAREN_CLOSE)) i = close + 1 } else { i = text.length @@ -251,13 +251,13 @@ class EnvSpecLexer : LexerBase() { } if (i < text.length && text[i] == '(') { - queue.add(QueuedToken(start + i, start + i + 1, EnvSpecTokenTypes.EQUALS)) + queue.add(QueuedToken(start + i, start + i + 1, EnvSpecTokenTypes.PAREN_OPEN)) val close = findClosingParen(text, i) if (close > i + 1) { enqueueDecoratorArgsTokens(start + i + 1, text.substring(i + 1, close)) } if (close < text.length) { - queue.add(QueuedToken(start + close, start + close + 1, EnvSpecTokenTypes.EQUALS)) + queue.add(QueuedToken(start + close, start + close + 1, EnvSpecTokenTypes.PAREN_CLOSE)) i = close + 1 } else { i = text.length diff --git a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecSyntaxHighlighter.kt b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecSyntaxHighlighter.kt index b2022a4b..7e0e7ba5 100644 --- a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecSyntaxHighlighter.kt +++ b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecSyntaxHighlighter.kt @@ -24,6 +24,8 @@ class EnvSpecSyntaxHighlighter : SyntaxHighlighterBase() { EnvSpecTokenTypes.EXPORT_KEYWORD -> KEYWORD_KEYS EnvSpecTokenTypes.ENV_KEY -> INSTANCE_FIELD_KEYS EnvSpecTokenTypes.EQUALS -> OPERATION_SIGN_KEYS + EnvSpecTokenTypes.PAREN_OPEN -> OPERATION_SIGN_KEYS + EnvSpecTokenTypes.PAREN_CLOSE -> OPERATION_SIGN_KEYS EnvSpecTokenTypes.ENV_VALUE -> STRING_KEYS EnvSpecTokenTypes.VALUE_FUNCTION -> FUNCTION_CALL_KEYS EnvSpecTokenTypes.VALUE_REFERENCE -> VALUE_REFERENCE_KEYS diff --git a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecTokenTypes.kt b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecTokenTypes.kt index 8ed13475..77a77fdb 100644 --- a/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecTokenTypes.kt +++ b/packages/intellij-plugin/src/main/kotlin/dev/dmno/envspec/EnvSpecTokenTypes.kt @@ -43,6 +43,12 @@ object EnvSpecTokenTypes { @JvmField val EQUALS: IElementType = object : IElementType("ENV_SPEC_EQUALS", EnvSpecLanguage) {} + @JvmField + val PAREN_OPEN: IElementType = object : IElementType("ENV_SPEC_PAREN_OPEN", EnvSpecLanguage) {} + + @JvmField + val PAREN_CLOSE: IElementType = object : IElementType("ENV_SPEC_PAREN_CLOSE", EnvSpecLanguage) {} + @JvmField val ENV_VALUE: IElementType = object : IElementType("ENV_SPEC_VALUE", EnvSpecLanguage) {} diff --git a/packages/vscode-plugin/src/completion-core.ts b/packages/vscode-plugin/src/completion-core.ts index cb540f69..ad9a4b7d 100644 --- a/packages/vscode-plugin/src/completion-core.ts +++ b/packages/vscode-plugin/src/completion-core.ts @@ -1,4 +1,5 @@ import type { DataTypeInfo, DecoratorInfo } from './intellisense-catalog'; +import { getDecoratorCommentText } from './diagnostics-core'; type LineDocument = { lineCount: number; @@ -15,39 +16,9 @@ const INCOMPATIBLE_DECORATORS = new Map>([ ['public', new Set(['sensitive'])], ]); -function stripInlineComment(value: string) { - let quote: '"' | '\'' | '' = ''; - - for (let i = 0; i < value.length; i += 1) { - const char = value[i]; - if (quote) { - if (char === quote) quote = ''; - continue; - } - - if (char === '"' || char === '\'') { - quote = char; - continue; - } - - if (char === '#' && (i === 0 || /\s/.test(value[i - 1]))) { - return value.slice(0, i).trimEnd(); - } - } - - return value.trim(); -} - +/** Thin wrapper kept for API compatibility with existing callers and tests. */ export function getDecoratorCommentPrefix(lineText: string) { - const match = lineText.match(/^\s*#\s*(@.*)$/); - if (!match) return undefined; - - const commentText = match[1]; - - if (/^@[A-Za-z]+:/.test(commentText)) return undefined; - if (/^@[A-Za-z]+\s+[^@#]/.test(commentText)) return undefined; - - return stripInlineComment(commentText); + return getDecoratorCommentText(lineText); } function splitArgs(input: string) { diff --git a/packages/vscode-plugin/src/diagnostics-core.ts b/packages/vscode-plugin/src/diagnostics-core.ts index 1b5814cd..e7b1a198 100644 --- a/packages/vscode-plugin/src/diagnostics-core.ts +++ b/packages/vscode-plugin/src/diagnostics-core.ts @@ -169,6 +169,8 @@ function getDecoratorCommentText(lineText: string) { return stripInlineComment(commentText); } +export { getDecoratorCommentText }; + export function getTypeInfoFromPrecedingComments(document: LineDocument, lineNumber: number) { const commentBlock = getPrecedingCommentBlock(document, lineNumber);