Skip to content

Commit

Permalink
resolve: highlight missing namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
gregsh committed Nov 17, 2020
1 parent f5f6db8 commit 6cdd9c0
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 87 deletions.
4 changes: 2 additions & 2 deletions src/clojure-constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ object ClojureConstants {
@JvmStatic val DEF_ALIKE_SYMBOLS = "\\s+".toRegex().split("""
def defn defn- defmacro defonce deftype defrecord defstruct defmulti defprotocol
def-aset definline definterface
define defcurried deftype* defrecord* create-ns
define defcurried deftype* defrecord*
""".trim()).toSet()

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

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

@JvmStatic val TYPE_META_ALIASES = "\\s+".toRegex().split("""
Expand Down
27 changes: 15 additions & 12 deletions src/lang/clojure-inspections.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ import com.intellij.psi.PsiPolyVariantReference
import org.intellij.clojure.ClojureConstants
import org.intellij.clojure.ClojureConstants.SYMBOLIC_VALUES
import org.intellij.clojure.psi.*
import org.intellij.clojure.psi.impl.CFileImpl
import org.intellij.clojure.psi.impl.placeLanguage
import org.intellij.clojure.psi.impl.*
import org.intellij.clojure.tools.Tool
import org.intellij.clojure.util.elementType
import org.intellij.clojure.util.iterate
import org.intellij.clojure.util.jbIt
import org.intellij.clojure.util.parents
import org.intellij.clojure.util.*
import kotlin.collections.component1
import kotlin.collections.component2

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

val checkBadNS = { it : PsiElement? -> o.parent !is CSymbol &&
it.asCTarget?.key?.run { type == "ns" && namespace == "" } == true &&
it.forceXTarget.let { it != null && !it.canNavigate() }}
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 }

val qualifier = reference.qualifier?.apply {
if (this.reference?.resolve() == null) return }

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

private fun suppressResolve(o: CReaderMacro, invalidResolve: Boolean) = when (o.firstChild.elementType) {
ClojureTypes.C_QUOTE, ClojureTypes.C_SYNTAX_QUOTE -> true
ClojureTypes.C_SHARP_QUOTE -> invalidResolve
private fun suppressResolve(o: CReaderMacro, invalid: Boolean, badNS : Boolean) = when (o.firstChild.elementType) {
ClojureTypes.C_QUOTE -> !badNS
ClojureTypes.C_SYNTAX_QUOTE -> true
ClojureTypes.C_SHARP_QUOTE -> invalid || badNS
ClojureTypes.C_SHARP_SYM -> SYMBOLIC_VALUES.contains((o.parent as? CSymbol)?.name)
else -> false
}
Expand Down
52 changes: 28 additions & 24 deletions src/lang/clojure-psi-fileimpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ open class CFileImpl(viewProvider: FileViewProvider, language: Language) :
else -> fileStubForced
}
}
internal val fileStubForced: CFileStub?
internal val fileStubForced: CFileStub
get() = fileStubRef?.get() ?: run {
val stub = if (virtualFile !is VirtualFileWithId) buildStubTree(this)
else StubTreeLoader.getInstance().readFromVFile(project, virtualFile)?.root as? CFileStub ?: buildStubTree(this)
Expand Down Expand Up @@ -110,7 +110,7 @@ open class CFileImpl(viewProvider: FileViewProvider, language: Language) :
val helper = RoleHelper()
helper.assignRoles(this, curState != null && curState.timeStamp < 0)
val definitions = cljTraverser().traverse()
.filter { (it as? CComposite)?.roleImpl == Role.DEF }
.filter { (it as? CComposite)?.dataImpl is IDef }
.filter(CList::class).toList()
val state = State(curTimeStamp, helper.fileNS, definitions, helper.imports)
myState = state
Expand Down Expand Up @@ -343,7 +343,7 @@ private fun processSyntheticDeclarations(def: IDef, private: Boolean,
private class RoleHelper {
val langStack = ArrayDeque<Dialect>()
val nsReader = NSReader(this)
val fileNS: String get() = nsReader.fileNS
val fileNS: String get() = nsReader.fileNS ?: ClojureConstants.NS_USER
val imports: List<Imports> get() = nsReader.result

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

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

if (firstTime) {
Expand Down Expand Up @@ -396,14 +396,13 @@ private class RoleHelper {
val ns = first.qualifier?.name?.let { resolveAlias(it) } ?:
if (seenDefs.contains(firstName.withNamespace(fileNS))) fileNS else langKind.coreNs
setData(first.qualifier, ns)
val nameSym = (first.nextForm as? CSymbol)?.apply { initFlags(this) }
if (ClojureConstants.DEF_ALIKE_SYMBOLS.contains(firstName) && ns == langKind.coreNs ||
firstName != "defmethod" &&
firstName.startsWith("def") && firstName != "default" && firstName != "def" /* clojure.spec/def */) {
val nameSym = first.nextForm as? CSymbol
if (nameSym != null && nameSym.firstChild !is CReaderMacro ) {
if (nameSym != null && !nameSym.fastFlagIsSet(FLAG_QUOTED) && !nameSym.fastFlagIsSet(FLAG_UNQUOTED)) {
// optimization: delay up until the end, so that other threads may skip this
val type = if (firstName == "create-ns") "ns" else firstName
val key = SymKey(nameSym.name, resolveAlias(nameSym.qualifier?.name) ?: fileNS, type)
val key = SymKey(nameSym.name, resolveAlias(nameSym.qualifier?.name) ?: fileNS, firstName)
setData(nameSym.qualifier, key.namespace)
setData(nameSym, Role.NAME)
delayedDefs[e] = createDef(e, nameSym, key)
Expand All @@ -429,7 +428,6 @@ private class RoleHelper {
seenDefs.add(key.name)
}
else if (ClojureConstants.NS_ALIKE_SYMBOLS.contains(firstName) && ns == langKind.coreNs) {
setData(e, Role.NS) // prevents deep traversal for e
processNSElement(e)
}
else if (ClojureConstants.LET_ALIKE_SYMBOLS.contains(firstName) && ns == langKind.coreNs) {
Expand Down Expand Up @@ -566,23 +564,26 @@ private class RoleHelper {
}

private class NSReader(val helper: RoleHelper) {
var fileNS: String = ClojureConstants.NS_USER
var fileNS: String? = null
val result = mutableListOf<Imports>()

fun processElement(e: CListBase) {
val nsType = (e as CList).first!!.name
var imports: MutableList<Imports>? = null
if (nsType == "in-ns" || nsType == "ns") {
val nameSym = e.first.nextForm as? CSymbol
val nameSym = e.first.nextForm as? CSymbol
val nsName = nameSym?.name
val nsQuotedOK = (nsType == "ns") != nameSym.fastFlagIsSet(FLAG_QUOTED)
if (nsName != null && nsQuotedOK && (nsType == "ns" || nsType == "create-ns" || nsType == "in-ns")) {
imports = mutableListOf()
if (nameSym != null) {
val name = nameSym.name
setData(nameSym, Role.NAME)
setData(e, NSDef(SymKey(name, "", "ns"), imports))
if (result.isEmpty()) {
fileNS = name
}
setData(nameSym, Role.NAME)
setData(e, NSDef(SymKey(nsName, "", "ns"), imports))
if (fileNS == null && nsType != "create-ns" && e.parentForm == null) {
fileNS = nsName
}
if (nsType != "ns") return
}
else {
setData(e, Role.NS)
}
val hasRC = e.cljTraverser().filter(CListBase::class.java)
.reduce(false, { flag, it -> helper.processRCParenForm(it) || flag })
Expand Down Expand Up @@ -618,7 +619,8 @@ private class NSReader(val helper: RoleHelper) {
}
if (imports.isEmpty()) return null
val scope = e.parentForms.skip(1).filter { it.fastRole != Role.RCOND && it.fastRole != Role.RCOND_S }.first()
return Imports(imports, dialect, e.textRange, scope?.textRange?.endOffset ?: -1)
val range = TextRange(e.first!!.textRange.endOffset, e.textRange.endOffset)
return Imports(imports, dialect, range, scope?.textRange?.endOffset ?: -1)
}

fun readNSElement2(root: CListBase, traverser: SyntaxTraverser<PsiElement>, nsType: String, inNs: Boolean, dialect: Dialect): List<Import> {
Expand Down Expand Up @@ -666,8 +668,8 @@ private class NSReader(val helper: RoleHelper) {
val iterator = content.iterator()
val aliasSym = iterator.safeNext() as? CSymbol ?: return emptyList()
val nsSym = iterator.safeNext() as? CSymbol
val aliasQuoted = aliasSym.fastFlags and FLAG_QUOTED != 0
val nsQuoted = nsSym.fastFlags and FLAG_QUOTED != 0
val aliasQuoted = aliasSym.fastFlagIsSet(FLAG_QUOTED)
val nsQuoted = nsSym.fastFlagIsSet(FLAG_QUOTED)
val namespace = if (nsQuoted) nsSym?.name ?: "" else ""
if (aliasQuoted) {
setResolveTo(aliasSym, SymKey(aliasSym.name, namespace, "alias"))
Expand Down Expand Up @@ -731,8 +733,10 @@ private class NSReader(val helper: RoleHelper) {
for (item in iterator) {
when (item) {
is CKeyword -> if (item.name == "as") iterator.safeNext() // ignore the next form to get it highlighted
is CSymbol -> addImport(item, "")
is CLVForm -> if (inNs == (item.fastFlags and FLAG_QUOTED == 0)) {
is CSymbol -> if (inNs != item.fastFlagIsSet(FLAG_QUOTED)) {
addImport(item, "")
}
is CLVForm -> if (inNs != item.fastFlagIsSet(FLAG_QUOTED)) {
if (item is CVec && (item.childForm(CKeyword::class) != null || item.childForm(CLVForm::class) == null)) {
addImport(item, "")
}
Expand Down
1 change: 1 addition & 0 deletions src/lang/clojure-psi-genimpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ fun newLeafPsiElement(project: Project, s: String): PsiElement =

val PsiElement?.fastRole: Role get() = (this as? CComposite)?.roleImpl ?: Role.NONE
val PsiElement?.fastFlags: Int get() = (this as? CComposite)?.flagsImpl ?: 0
fun PsiElement?.fastFlagIsSet(flag: Int): Boolean = fastFlags and flag == flag
val CList?.fastDef: IDef?
get() = (this as? CListBase)?.run {
((this as CComposite).dataImpl as? IDef)?.run {
Expand Down
12 changes: 8 additions & 4 deletions src/lang/clojure-psi-index.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import java.io.DataOutput
/**
* @author gregsh
*/
val VERSION = 202
val VERSION = 204

val NS_INDEX = ID.create<String, Unit>("clojure.ns")
val DEF_INDEX = ID.create<String, Unit>("clojure.def")
Expand All @@ -43,21 +43,25 @@ val KEYWORD_FQN_INDEX = ID.create<String, Unit>("clojure.keyword.fqn")
class ClojureNSIndex : ClojureUnitIndex() {
override fun getName(): ID<String, Unit> = NS_INDEX
override fun index(file: CFile): MutableMap<String, Unit> {
return mutableMapOf(file.namespace to Unit)
val result = mutableMapOf(file.namespace to Unit)
file.defs().filter { it.def!!.type == "ns" }.forEach {
result[it.def!!.name] = Unit
}
return result
}
}

class ClojureDefIndex : ClojureUnitIndex() {
override fun getName(): ID<String, Unit> = DEF_INDEX
override fun index(file: CFile): MutableMap<String, Unit> {
return file.defs().map { it.def!!.name }.toMap { Unit }
return file.defs().filter { it.def!!.type != "ns" }. map { it.def!!.name }.toMap { Unit }
}
}

class ClojureDefFqnIndex : ClojureUnitIndex() {
override fun getName(): ID<String, Unit> = DEF_FQN_INDEX
override fun index(file: CFile): MutableMap<String, Unit> {
return file.defs().map {it.def!!.qualifiedName }.toMap { Unit }
return file.defs().filter { it.def!!.type != "ns" }.map { it.def!!.qualifiedName }.toMap { Unit }
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/lang/clojure-psi-stubs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import kotlin.reflect.jvm.internal.impl.utils.SmartList
/**
* @author gregsh
*/
private val VERSION: Int = 6
val VERSION: Int = 7

class ClojureStubBuilder : BinaryFileStubBuilder {
override fun getStubVersion() = VERSION
Expand Down Expand Up @@ -106,7 +106,7 @@ class CListStub(val key: SymKey,
?.registerListStub(this)
}

override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
override fun getStubType() = SERIALIZER
override fun toString() = key.toString()

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

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

override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
override fun getStubType() = SERIALIZER
override fun toString() = args.toString()

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

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

override fun getStubType() = SERIALIZER as ObjectStubSerializer<*, Stub>
override fun getStubType() = SERIALIZER
override fun toString() = map.toString()

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

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

companion object {
val SERIALIZER = object : ObjectStubSerializer<CImportStub, CStub> {
Expand Down
28 changes: 14 additions & 14 deletions src/lang/clojure-psi-symbols.kt
Original file line number Diff line number Diff line change
Expand Up @@ -286,25 +286,25 @@ private fun wrapWithNavigationElement(project: Project, key: SymKey): PsiElement
internal fun wrapWithNavigationElement(project: Project, key: SymKey, file: VirtualFile?): NavigatablePsiElement {
val adjusted = key.adjustKeyForNavigation()
fun <C : CForm> locate(k: SymKey, clazz: KClass<C>): (CFile) -> Navigatable? = { f ->
f.cljTraverser().traverse().filter(clazz).find {
if (k.type == "keyword") it is CKeyword && it.namespace == k.namespace
else it is CList && it.def?.run { name == k.name && namespace == k.namespace } ?: false
}.let {
when {
k == key -> it
key.type == "field" -> {
it.findChild(Role.FIELD_VEC).iterate().find { it is CSymbol && it.name == key.name } as? CSymbol
}
key.type == "method" -> {
it.childForms(CList::class).find { it?.def?.let { it.name == key.name && it.type == key.type} == true }
}
else -> it
val form = f.cljTraverser().traverse().filter(clazz).find {
when (k.type) {
"keyword" -> it is CKeyword && it.run { name == k.name && namespace == k.namespace }
else -> it is CList && it.def?.run { name == k.name && namespace == k.namespace } ?: false
}
}
when {
k == key -> form
key.type == "field" -> {
form.findChild(Role.FIELD_VEC).iterate().find { form is CSymbol && form.name == key.name } as? CSymbol
}
key.type == "method" -> {
form.childForms(CList::class).find { it?.def?.let { it.name == key.name && it.type == key.type } == true }
}
else -> form
}
}

return when (key.type) {
"ns" -> CPomTargetElement(project, XTarget(project, key, file) { it })
"keyword" -> CPomTargetElement(project, XTarget(project, key, file, locate(adjusted, CKeywordBase::class)))
else -> CPomTargetElement(project, XTarget(project, key, file, locate(adjusted, CListBase::class)))
}
Expand Down
2 changes: 1 addition & 1 deletion testData/highlighting/ClojureFixes.clj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
)
(do
(ns nsns (:require [clojure.core]))
(require '[clojure.missing]))
(require '[<warning descr="unable to resolve 'clojure.missing'">clojure.missing</warning>]))

(do
(use '[clojure.set :as s1])
Expand Down
Loading

0 comments on commit 6cdd9c0

Please sign in to comment.