1+ package com.serchinastico.lin.dsl
2+
3+ import com.android.tools.lint.detector.api.*
4+ import org.jetbrains.uast.*
5+ import java.util.*
6+ import kotlin.reflect.KClass
7+
8+ data class LinRule (val issueBuilder : IssueBuilder ) {
9+
10+ private var lNode: LNode <* >? = null
11+
12+ val applicableTypes: List <Class <out UElement >>
13+ get() = lNode?.applicableType?.let { listOf (it) } ? : emptyList()
14+
15+ fun matches (node : UElement ): Boolean = lNode?.match(node) ? : false
16+
17+ fun file (block : LNode .LFile .() -> LNode .LFile ): LinRule {
18+ lNode = LNode .LFile ().block()
19+ return this
20+ }
21+
22+ fun import (block : LNode .LImport .() -> LNode .LImport ): LinRule {
23+ lNode = LNode .LImport ().block()
24+ return this
25+ }
26+
27+ fun type (block : LNode .LType .() -> LNode .LType ): LinRule {
28+ lNode = LNode .LType ().block()
29+ return this
30+ }
31+
32+ fun switch (block : LNode .LSwitchExpression .() -> LNode .LSwitchExpression ): LinRule {
33+ lNode = LNode .LSwitchExpression ().block()
34+ return this
35+ }
36+
37+ fun call (block : LNode .LCallExpression .() -> LNode .LCallExpression ): LinRule {
38+ lNode = LNode .LCallExpression ().block()
39+ return this
40+ }
41+
42+ fun field (block : LNode .LField .() -> LNode .LField ): LinRule {
43+ lNode = LNode .LField ().block()
44+ return this
45+ }
46+ }
47+
48+ sealed class LNode <T : UElement > {
49+
50+ abstract val applicableType: Class <out T >
51+ internal val children: MutableList <LNode <* >> = mutableListOf ()
52+ private var suchThatPredicate: ((T ) -> Boolean )? = null
53+
54+ fun match (element : UElement ): Boolean {
55+ val predicate = suchThatPredicate ? : { true }
56+ return predicate.invoke(applicableType.cast(element)) && children.all { child ->
57+ child.lookForChildren(element).any { child.match(it) }
58+ }
59+ }
60+
61+ fun <S : LNode <* >> suchThat (predicate : (T ) -> Boolean ): S {
62+ suchThatPredicate = predicate
63+ return this as S
64+ }
65+
66+ open fun lookForChildren (element : UElement ): List <T > = emptyList()
67+
68+ class LFile : LNode <UFile >() {
69+
70+ fun import (block : LImport .() -> LImport ): LFile {
71+ children.add(LImport ().block())
72+ return this
73+ }
74+
75+ fun type (block : LType .() -> LType ): LFile {
76+ children.add(LType ().block())
77+ return this
78+ }
79+
80+ override val applicableType: Class <out UFile > = UFile ::class .java
81+ }
82+
83+ class LImport : LNode <UImportStatement >() {
84+ override val applicableType: Class <out UImportStatement > = UImportStatement ::class .java
85+
86+ override fun lookForChildren (element : UElement ): List <UImportStatement > = when (element) {
87+ is UFile -> element.imports
88+ else -> super .lookForChildren(element)
89+ }
90+ }
91+
92+ class LType : LNode <UClass >() {
93+ override val applicableType: Class <out UClass > = UClass ::class .java
94+
95+ override fun lookForChildren (element : UElement ): List <UClass > = when (element) {
96+ is UFile -> element.classes
97+ else -> super .lookForChildren(element)
98+ }
99+
100+ fun switch (block : LSwitchExpression .() -> LSwitchExpression ): LType {
101+ children.add(LSwitchExpression ().block())
102+ return this
103+ }
104+
105+ fun calls (block : LCallExpression .() -> LCallExpression ): LType {
106+ children.add(LCallExpression ().block())
107+ return this
108+ }
109+
110+ fun field (block : LField .() -> LField ): LType {
111+ children.add(LField ().block())
112+ return this
113+ }
114+ }
115+
116+ class LSwitchExpression : LNode <USwitchExpression >() {
117+ override val applicableType: Class <out USwitchExpression > = USwitchExpression ::class .java
118+ }
119+
120+ class LCallExpression : LNode <UCallExpression >() {
121+ override val applicableType: Class <out UCallExpression > = UCallExpression ::class .java
122+ }
123+
124+ class LField : LNode <UField >() {
125+ override val applicableType: Class <out UField > = UField ::class .java
126+ }
127+ }
128+
129+ fun rule (issueBuilder : IssueBuilder , block : LinRule .() -> LinRule ): LinRule = LinRule (issueBuilder).block()
130+
131+ fun main (args : Array <String >) {
132+ val detectorScope = Scope .JAVA_FILE_SCOPE
133+
134+ rule(
135+ issue(
136+ detectorScope,
137+ " Framework classes to get or store data should never be called from Activities, Fragments or any other" +
138+ " Android related view." ,
139+ " Your Android classes should not be responsible for retrieving or storing information, that should be " +
140+ " responsibility of another classes." ,
141+ Category .INTEROPERABILITY
142+ )
143+ ) {
144+ file {
145+ import { suchThat { it.isFrameworkLibraryImport } }
146+ type { suchThat { node -> node.uastSuperTypes.any { it.isAndroidFrameworkType } } }
147+ }
148+ }
149+ }
150+
151+ fun issue (
152+ scope : EnumSet <Scope >,
153+ description : String ,
154+ explanation : String ,
155+ category : Category
156+ ): IssueBuilder {
157+ return IssueBuilder (scope, description, explanation, category)
158+ }
159+
160+ data class IssueBuilder (
161+ val scope : EnumSet <Scope >,
162+ val description : String ,
163+ val explanation : String ,
164+ val category : Category ,
165+ var priority : Int = 5 ,
166+ var severity : Severity = Severity .ERROR
167+ ) {
168+ fun <T : Detector > build (detectorClass : KClass <T >): Issue =
169+ Issue .create(
170+ detectorClass.simpleName ? : " RuleWithNoId" ,
171+ description,
172+ explanation,
173+ category,
174+ priority,
175+ severity,
176+ Implementation (detectorClass.java, scope)
177+ )
178+ }
0 commit comments