Skip to content

Commit 6cdd9c0

Browse files
committed
resolve: highlight missing namespaces
1 parent f5f6db8 commit 6cdd9c0

File tree

9 files changed

+107
-87
lines changed

9 files changed

+107
-87
lines changed

src/clojure-constants.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ object ClojureConstants {
7272
@JvmStatic val DEF_ALIKE_SYMBOLS = "\\s+".toRegex().split("""
7373
def defn defn- defmacro defonce deftype defrecord defstruct defmulti defprotocol
7474
def-aset definline definterface
75-
define defcurried deftype* defrecord* create-ns
75+
define defcurried deftype* defrecord*
7676
""".trim()).toSet()
7777

7878
@JvmStatic val FN_ALIKE_SYMBOLS = "\\s+".toRegex().split("""fn fn* rfn""").toSet()
@@ -85,7 +85,7 @@ object ClojureConstants {
8585
""".trim()).toSet()
8686

8787
@JvmStatic val NS_ALIKE_SYMBOLS = "\\s+".toRegex().split("""
88-
ns in-ns import require require-macros use refer refer-clojure alias
88+
ns in-ns create-ns import require require-macros use refer refer-clojure alias
8989
""".trim()).toSet()
9090

9191
@JvmStatic val TYPE_META_ALIASES = "\\s+".toRegex().split("""

src/lang/clojure-inspections.kt

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,9 @@ import com.intellij.psi.PsiPolyVariantReference
2424
import org.intellij.clojure.ClojureConstants
2525
import org.intellij.clojure.ClojureConstants.SYMBOLIC_VALUES
2626
import org.intellij.clojure.psi.*
27-
import org.intellij.clojure.psi.impl.CFileImpl
28-
import org.intellij.clojure.psi.impl.placeLanguage
27+
import org.intellij.clojure.psi.impl.*
2928
import org.intellij.clojure.tools.Tool
30-
import org.intellij.clojure.util.elementType
31-
import org.intellij.clojure.util.iterate
32-
import org.intellij.clojure.util.jbIt
33-
import org.intellij.clojure.util.parents
29+
import org.intellij.clojure.util.*
3430
import kotlin.collections.component1
3531
import kotlin.collections.component2
3632

@@ -62,9 +58,13 @@ class ClojureResolveInspection : LocalInspectionTool() {
6258
override fun visitSymbol(o: CSymbol) {
6359
val reference = o.reference
6460
val multiResolve = (reference as PsiPolyVariantReference).multiResolve(false)
65-
val (valid, invalid) = multiResolve.jbIt().reduce(arrayOf(0, 0)) { arr, it -> arr[if (it.isValidResult) 0 else 1] ++; arr }
6661
if (o.getUserData(RESOLVE_SKIPPED) != null) return
6762

63+
val checkBadNS = { it : PsiElement? -> o.parent !is CSymbol &&
64+
it.asCTarget?.key?.run { type == "ns" && namespace == "" } == true &&
65+
it.forceXTarget.let { it != null && !it.canNavigate() }}
66+
val (valid, invalid, badNS) = multiResolve.jbIt().reduce(arrayOf(0, 0, 0)) { arr, it -> arr[if (it.isValidResult) if (checkBadNS(it.element)) 2 else 0 else 1] ++; arr }
67+
6868
val qualifier = reference.qualifier?.apply {
6969
if (this.reference?.resolve() == null) return }
7070

@@ -77,8 +77,10 @@ class ClojureResolveInspection : LocalInspectionTool() {
7777
if (o.parent is CSymbol && o.parent.parent is CKeyword &&
7878
o.parent.prevSibling?.elementType == ClojureTypes.C_COLON) return
7979
val quotesAndComments = o.parents().filter {
80-
it is CMetadata || it is CForm && (it.flags and FLAG_COMMENTED != 0 ||
81-
it.role != Role.RCOND && it.iterate(CReaderMacro::class).find { suppressResolve(it, invalid != 0) } != null)
80+
it is CMetadata ||
81+
it.fastFlagIsSet(FLAG_COMMENTED) ||
82+
it is CReaderMacro && it.firstChild.elementType == ClojureTypes.C_SHARP_NS ||
83+
it.role != Role.RCOND && it.iterate(CReaderMacro::class).find { suppressResolve(it, invalid != 0, badNS != 0) } != null
8284
}.first()
8385
if (quotesAndComments != null) return
8486
holder.registerProblem(reference, "unable to resolve '${reference.referenceName}'", ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
@@ -87,9 +89,10 @@ class ClojureResolveInspection : LocalInspectionTool() {
8789
}
8890
}
8991

90-
private fun suppressResolve(o: CReaderMacro, invalidResolve: Boolean) = when (o.firstChild.elementType) {
91-
ClojureTypes.C_QUOTE, ClojureTypes.C_SYNTAX_QUOTE -> true
92-
ClojureTypes.C_SHARP_QUOTE -> invalidResolve
92+
private fun suppressResolve(o: CReaderMacro, invalid: Boolean, badNS : Boolean) = when (o.firstChild.elementType) {
93+
ClojureTypes.C_QUOTE -> !badNS
94+
ClojureTypes.C_SYNTAX_QUOTE -> true
95+
ClojureTypes.C_SHARP_QUOTE -> invalid || badNS
9396
ClojureTypes.C_SHARP_SYM -> SYMBOLIC_VALUES.contains((o.parent as? CSymbol)?.name)
9497
else -> false
9598
}

src/lang/clojure-psi-fileimpl.kt

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ open class CFileImpl(viewProvider: FileViewProvider, language: Language) :
6565
else -> fileStubForced
6666
}
6767
}
68-
internal val fileStubForced: CFileStub?
68+
internal val fileStubForced: CFileStub
6969
get() = fileStubRef?.get() ?: run {
7070
val stub = if (virtualFile !is VirtualFileWithId) buildStubTree(this)
7171
else StubTreeLoader.getInstance().readFromVFile(project, virtualFile)?.root as? CFileStub ?: buildStubTree(this)
@@ -110,7 +110,7 @@ open class CFileImpl(viewProvider: FileViewProvider, language: Language) :
110110
val helper = RoleHelper()
111111
helper.assignRoles(this, curState != null && curState.timeStamp < 0)
112112
val definitions = cljTraverser().traverse()
113-
.filter { (it as? CComposite)?.roleImpl == Role.DEF }
113+
.filter { (it as? CComposite)?.dataImpl is IDef }
114114
.filter(CList::class).toList()
115115
val state = State(curTimeStamp, helper.fileNS, definitions, helper.imports)
116116
myState = state
@@ -343,7 +343,7 @@ private fun processSyntheticDeclarations(def: IDef, private: Boolean,
343343
private class RoleHelper {
344344
val langStack = ArrayDeque<Dialect>()
345345
val nsReader = NSReader(this)
346-
val fileNS: String get() = nsReader.fileNS
346+
val fileNS: String get() = nsReader.fileNS ?: ClojureConstants.NS_USER
347347
val imports: List<Imports> get() = nsReader.result
348348

349349
fun currentLangKind() = langStack.peek()!!
@@ -362,7 +362,7 @@ private class RoleHelper {
362362
langStack.push(if (file.language == ClojureScriptLanguage) Dialect.CLJS else Dialect.CLJ)
363363

364364
val s = file.cljTraverser().expand {
365-
it !is CListBase || (it as CComposite).roleImpl != Role.DEF && (it as CComposite).roleImpl != Role.NS
365+
it !is CListBase || (it as CComposite).roleImpl.let { r -> r != Role.DEF && r != Role.NS }
366366
}.traverse()
367367

368368
if (firstTime) {
@@ -396,14 +396,13 @@ private class RoleHelper {
396396
val ns = first.qualifier?.name?.let { resolveAlias(it) } ?:
397397
if (seenDefs.contains(firstName.withNamespace(fileNS))) fileNS else langKind.coreNs
398398
setData(first.qualifier, ns)
399+
val nameSym = (first.nextForm as? CSymbol)?.apply { initFlags(this) }
399400
if (ClojureConstants.DEF_ALIKE_SYMBOLS.contains(firstName) && ns == langKind.coreNs ||
400401
firstName != "defmethod" &&
401402
firstName.startsWith("def") && firstName != "default" && firstName != "def" /* clojure.spec/def */) {
402-
val nameSym = first.nextForm as? CSymbol
403-
if (nameSym != null && nameSym.firstChild !is CReaderMacro ) {
403+
if (nameSym != null && !nameSym.fastFlagIsSet(FLAG_QUOTED) && !nameSym.fastFlagIsSet(FLAG_UNQUOTED)) {
404404
// optimization: delay up until the end, so that other threads may skip this
405-
val type = if (firstName == "create-ns") "ns" else firstName
406-
val key = SymKey(nameSym.name, resolveAlias(nameSym.qualifier?.name) ?: fileNS, type)
405+
val key = SymKey(nameSym.name, resolveAlias(nameSym.qualifier?.name) ?: fileNS, firstName)
407406
setData(nameSym.qualifier, key.namespace)
408407
setData(nameSym, Role.NAME)
409408
delayedDefs[e] = createDef(e, nameSym, key)
@@ -429,7 +428,6 @@ private class RoleHelper {
429428
seenDefs.add(key.name)
430429
}
431430
else if (ClojureConstants.NS_ALIKE_SYMBOLS.contains(firstName) && ns == langKind.coreNs) {
432-
setData(e, Role.NS) // prevents deep traversal for e
433431
processNSElement(e)
434432
}
435433
else if (ClojureConstants.LET_ALIKE_SYMBOLS.contains(firstName) && ns == langKind.coreNs) {
@@ -566,23 +564,26 @@ private class RoleHelper {
566564
}
567565

568566
private class NSReader(val helper: RoleHelper) {
569-
var fileNS: String = ClojureConstants.NS_USER
567+
var fileNS: String? = null
570568
val result = mutableListOf<Imports>()
571569

572570
fun processElement(e: CListBase) {
573571
val nsType = (e as CList).first!!.name
574572
var imports: MutableList<Imports>? = null
575-
if (nsType == "in-ns" || nsType == "ns") {
576-
val nameSym = e.first.nextForm as? CSymbol
573+
val nameSym = e.first.nextForm as? CSymbol
574+
val nsName = nameSym?.name
575+
val nsQuotedOK = (nsType == "ns") != nameSym.fastFlagIsSet(FLAG_QUOTED)
576+
if (nsName != null && nsQuotedOK && (nsType == "ns" || nsType == "create-ns" || nsType == "in-ns")) {
577577
imports = mutableListOf()
578-
if (nameSym != null) {
579-
val name = nameSym.name
580-
setData(nameSym, Role.NAME)
581-
setData(e, NSDef(SymKey(name, "", "ns"), imports))
582-
if (result.isEmpty()) {
583-
fileNS = name
584-
}
578+
setData(nameSym, Role.NAME)
579+
setData(e, NSDef(SymKey(nsName, "", "ns"), imports))
580+
if (fileNS == null && nsType != "create-ns" && e.parentForm == null) {
581+
fileNS = nsName
585582
}
583+
if (nsType != "ns") return
584+
}
585+
else {
586+
setData(e, Role.NS)
586587
}
587588
val hasRC = e.cljTraverser().filter(CListBase::class.java)
588589
.reduce(false, { flag, it -> helper.processRCParenForm(it) || flag })
@@ -618,7 +619,8 @@ private class NSReader(val helper: RoleHelper) {
618619
}
619620
if (imports.isEmpty()) return null
620621
val scope = e.parentForms.skip(1).filter { it.fastRole != Role.RCOND && it.fastRole != Role.RCOND_S }.first()
621-
return Imports(imports, dialect, e.textRange, scope?.textRange?.endOffset ?: -1)
622+
val range = TextRange(e.first!!.textRange.endOffset, e.textRange.endOffset)
623+
return Imports(imports, dialect, range, scope?.textRange?.endOffset ?: -1)
622624
}
623625

624626
fun readNSElement2(root: CListBase, traverser: SyntaxTraverser<PsiElement>, nsType: String, inNs: Boolean, dialect: Dialect): List<Import> {
@@ -666,8 +668,8 @@ private class NSReader(val helper: RoleHelper) {
666668
val iterator = content.iterator()
667669
val aliasSym = iterator.safeNext() as? CSymbol ?: return emptyList()
668670
val nsSym = iterator.safeNext() as? CSymbol
669-
val aliasQuoted = aliasSym.fastFlags and FLAG_QUOTED != 0
670-
val nsQuoted = nsSym.fastFlags and FLAG_QUOTED != 0
671+
val aliasQuoted = aliasSym.fastFlagIsSet(FLAG_QUOTED)
672+
val nsQuoted = nsSym.fastFlagIsSet(FLAG_QUOTED)
671673
val namespace = if (nsQuoted) nsSym?.name ?: "" else ""
672674
if (aliasQuoted) {
673675
setResolveTo(aliasSym, SymKey(aliasSym.name, namespace, "alias"))
@@ -731,8 +733,10 @@ private class NSReader(val helper: RoleHelper) {
731733
for (item in iterator) {
732734
when (item) {
733735
is CKeyword -> if (item.name == "as") iterator.safeNext() // ignore the next form to get it highlighted
734-
is CSymbol -> addImport(item, "")
735-
is CLVForm -> if (inNs == (item.fastFlags and FLAG_QUOTED == 0)) {
736+
is CSymbol -> if (inNs != item.fastFlagIsSet(FLAG_QUOTED)) {
737+
addImport(item, "")
738+
}
739+
is CLVForm -> if (inNs != item.fastFlagIsSet(FLAG_QUOTED)) {
736740
if (item is CVec && (item.childForm(CKeyword::class) != null || item.childForm(CLVForm::class) == null)) {
737741
addImport(item, "")
738742
}

src/lang/clojure-psi-genimpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ fun newLeafPsiElement(project: Project, s: String): PsiElement =
143143

144144
val PsiElement?.fastRole: Role get() = (this as? CComposite)?.roleImpl ?: Role.NONE
145145
val PsiElement?.fastFlags: Int get() = (this as? CComposite)?.flagsImpl ?: 0
146+
fun PsiElement?.fastFlagIsSet(flag: Int): Boolean = fastFlags and flag == flag
146147
val CList?.fastDef: IDef?
147148
get() = (this as? CListBase)?.run {
148149
((this as CComposite).dataImpl as? IDef)?.run {

src/lang/clojure-psi-index.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import java.io.DataOutput
3232
/**
3333
* @author gregsh
3434
*/
35-
val VERSION = 202
35+
val VERSION = 204
3636

3737
val NS_INDEX = ID.create<String, Unit>("clojure.ns")
3838
val DEF_INDEX = ID.create<String, Unit>("clojure.def")
@@ -43,21 +43,25 @@ val KEYWORD_FQN_INDEX = ID.create<String, Unit>("clojure.keyword.fqn")
4343
class ClojureNSIndex : ClojureUnitIndex() {
4444
override fun getName(): ID<String, Unit> = NS_INDEX
4545
override fun index(file: CFile): MutableMap<String, Unit> {
46-
return mutableMapOf(file.namespace to Unit)
46+
val result = mutableMapOf(file.namespace to Unit)
47+
file.defs().filter { it.def!!.type == "ns" }.forEach {
48+
result[it.def!!.name] = Unit
49+
}
50+
return result
4751
}
4852
}
4953

5054
class ClojureDefIndex : ClojureUnitIndex() {
5155
override fun getName(): ID<String, Unit> = DEF_INDEX
5256
override fun index(file: CFile): MutableMap<String, Unit> {
53-
return file.defs().map { it.def!!.name }.toMap { Unit }
57+
return file.defs().filter { it.def!!.type != "ns" }. map { it.def!!.name }.toMap { Unit }
5458
}
5559
}
5660

5761
class ClojureDefFqnIndex : ClojureUnitIndex() {
5862
override fun getName(): ID<String, Unit> = DEF_FQN_INDEX
5963
override fun index(file: CFile): MutableMap<String, Unit> {
60-
return file.defs().map {it.def!!.qualifiedName }.toMap { Unit }
64+
return file.defs().filter { it.def!!.type != "ns" }.map { it.def!!.qualifiedName }.toMap { Unit }
6165
}
6266
}
6367

src/lang/clojure-psi-stubs.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import kotlin.reflect.jvm.internal.impl.utils.SmartList
3737
/**
3838
* @author gregsh
3939
*/
40-
private val VERSION: Int = 6
40+
val VERSION: Int = 7
4141

4242
class ClojureStubBuilder : BinaryFileStubBuilder {
4343
override fun getStubVersion() = VERSION
@@ -106,7 +106,7 @@ class CListStub(val key: SymKey,
106106
?.registerListStub(this)
107107
}
108108

109-
override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
109+
override fun getStubType() = SERIALIZER
110110
override fun toString() = key.toString()
111111

112112
companion object {
@@ -127,7 +127,7 @@ class CListStub(val key: SymKey,
127127

128128
class CPrototypeStub(val args: List<Arg>, val typeHint: String?, parent: CStub?) : CStub(parent) {
129129

130-
override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
130+
override fun getStubType() = SERIALIZER
131131
override fun toString() = args.toString()
132132

133133
companion object {
@@ -154,7 +154,7 @@ class CPrototypeStub(val args: List<Arg>, val typeHint: String?, parent: CStub?)
154154

155155
class CMetaStub(val map: Map<String, String>, parent: CStub?) : CStub(parent) {
156156

157-
override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
157+
override fun getStubType() = SERIALIZER
158158
override fun toString() = map.toString()
159159

160160
companion object {
@@ -169,7 +169,7 @@ class CMetaStub(val map: Map<String, String>, parent: CStub?) : CStub(parent) {
169169
}
170170

171171
internal class CImportStub(val import: Import, val dialect: Dialect, parent: CStub?) : CStub(parent) {
172-
override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
172+
override fun getStubType() = SERIALIZER
173173

174174
companion object {
175175
val SERIALIZER = object : ObjectStubSerializer<CImportStub, CStub> {

src/lang/clojure-psi-symbols.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -286,25 +286,25 @@ private fun wrapWithNavigationElement(project: Project, key: SymKey): PsiElement
286286
internal fun wrapWithNavigationElement(project: Project, key: SymKey, file: VirtualFile?): NavigatablePsiElement {
287287
val adjusted = key.adjustKeyForNavigation()
288288
fun <C : CForm> locate(k: SymKey, clazz: KClass<C>): (CFile) -> Navigatable? = { f ->
289-
f.cljTraverser().traverse().filter(clazz).find {
290-
if (k.type == "keyword") it is CKeyword && it.namespace == k.namespace
291-
else it is CList && it.def?.run { name == k.name && namespace == k.namespace } ?: false
292-
}.let {
293-
when {
294-
k == key -> it
295-
key.type == "field" -> {
296-
it.findChild(Role.FIELD_VEC).iterate().find { it is CSymbol && it.name == key.name } as? CSymbol
297-
}
298-
key.type == "method" -> {
299-
it.childForms(CList::class).find { it?.def?.let { it.name == key.name && it.type == key.type} == true }
300-
}
301-
else -> it
289+
val form = f.cljTraverser().traverse().filter(clazz).find {
290+
when (k.type) {
291+
"keyword" -> it is CKeyword && it.run { name == k.name && namespace == k.namespace }
292+
else -> it is CList && it.def?.run { name == k.name && namespace == k.namespace } ?: false
302293
}
303294
}
295+
when {
296+
k == key -> form
297+
key.type == "field" -> {
298+
form.findChild(Role.FIELD_VEC).iterate().find { form is CSymbol && form.name == key.name } as? CSymbol
299+
}
300+
key.type == "method" -> {
301+
form.childForms(CList::class).find { it?.def?.let { it.name == key.name && it.type == key.type } == true }
302+
}
303+
else -> form
304+
}
304305
}
305306

306307
return when (key.type) {
307-
"ns" -> CPomTargetElement(project, XTarget(project, key, file) { it })
308308
"keyword" -> CPomTargetElement(project, XTarget(project, key, file, locate(adjusted, CKeywordBase::class)))
309309
else -> CPomTargetElement(project, XTarget(project, key, file, locate(adjusted, CListBase::class)))
310310
}

testData/highlighting/ClojureFixes.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
)
5050
(do
5151
(ns nsns (:require [clojure.core]))
52-
(require '[clojure.missing]))
52+
(require '[<warning descr="unable to resolve 'clojure.missing'">clojure.missing</warning>]))
5353

5454
(do
5555
(use '[clojure.set :as s1])

0 commit comments

Comments
 (0)