Skip to content
This repository
Browse code

Tidy up AstSelector code

  • Loading branch information...
commit f46e687cf65acc26e52cffea55e7faaa7b1e2c08 1 parent 35b8df4
Matt Russell authored January 12, 2012
1  .gitignore
@@ -17,3 +17,4 @@ docs/build
17 17
 .settings
18 18
 nohup.out
19 19
 .history
  20
+.cache
164  scalariform/src/main/scala/scalariform/astselect/AstSelector.scala
@@ -3,6 +3,8 @@ package scalariform.astselect
3 3
 import scalariform.lexer._
4 4
 import scalariform.parser._
5 5
 import scalariform.utils.Range
  6
+import scalariform.utils.Utils._
  7
+import scala.util.control.Exception._
6 8
 
7 9
 object AstSelector {
8 10
 
@@ -12,17 +14,15 @@ object AstSelector {
12 14
    * there is no strictly larger containing AST element.
13 15
    */
14 16
   def expandSelection(source: String, initialSelection: Range): Option[Range] =
15  
-    try {
  17
+    catching(classOf[ScalaParserException]).toOption {
16 18
       new AstSelector(source).expandSelection(initialSelection)
17  
-    } catch {
18  
-      case e: ScalaParserException ⇒ None
19 19
     }
20 20
 
21 21
   import Tokens._
22 22
 
23  
-  private val selectableXmls = Set(XML_NAME, XML_ATTR_VALUE, XML_PCDATA, XML_COMMENT, XML_UNPARSED, XML_PCDATA)
  23
+  private val selectableXmlTokens = Set(XML_NAME, XML_ATTR_VALUE, XML_PCDATA, XML_COMMENT, XML_UNPARSED, XML_PCDATA)
24 24
 
25  
-  private val nonSelectableAstNodes: Set[Class[_]] =
  25
+  private val nonSelectableAstNodes: Set[Class[_ <: AstNode]] =
26 26
     Set(
27 27
       classOf[AccessQualifier],
28 28
       classOf[CasePattern],
@@ -50,88 +50,104 @@ object AstSelector {
50 50
 }
51 51
 
52 52
 class AstSelector(source: String) {
  53
+
53 54
   import AstSelector._
54 55
 
55 56
   private val (hiddenTokenInfo, tokens) = ScalaLexer.tokeniseFull(source)
56  
-  import hiddenTokenInfo._
57 57
 
58  
-  private val parser = new ScalaParser(tokens.toArray)
59  
-  private val compilationUnitOpt = parser.safeParse(parser.compilationUnitOrScript)
  58
+  import hiddenTokenInfo._
60 59
 
61  
-  /**
62  
-   * A node's adjusted range includes any Scaladoc immediately before it.
63  
-   */
64  
-  private def adjustedNodeRange(node: AstNode): Option[Range] =
65  
-    if (node.isEmpty)
66  
-      None
67  
-    else {
68  
-      val nodeRange = node.rangeOpt.get
69  
-      val scaladocComments = getPriorHiddenTokens(node.firstToken).scalaDocComments
70  
-      scaladocComments.lastOption map { comment ⇒ nodeRange mergeWith comment.token.range } orElse Some(nodeRange)
  60
+  private val compilationUnitOpt: Option[CompilationUnit] = {
  61
+    val parser = new ScalaParser(tokens.toArray)
  62
+    parser.safeParse(parser.compilationUnitOrScript)
  63
+  }
  64
+  
  65
+  private val allTokens: List[Token] = tokens.flatMap { ordinaryToken ⇒
  66
+    inferredNewlines(ordinaryToken) match {
  67
+      case Some(hiddenTokens) ⇒ hiddenTokens.rawTokens
  68
+      case None               ⇒ hiddenPredecessors(ordinaryToken).rawTokens :+ ordinaryToken
71 69
     }
  70
+  }
72 71
 
73 72
   def expandSelection(initialSelection: Range): Option[Range] =
74 73
     expandToToken(initialSelection) orElse
  74
+      expandScaladocToAssociatedNode(initialSelection) orElse
75 75
       (compilationUnitOpt flatMap { expandToEnclosingAst(_, initialSelection, enclosingNodes = Nil) })
76 76
 
77  
-  private def expandToToken(initialSelection: Range): Option[Range] = {
78  
-    val Range(offset, length) = initialSelection
79  
-    for (ordinaryToken ← tokens) {
80  
-      val allTokens = inferredNewlines(ordinaryToken) match {
81  
-        case Some(hiddenTokens) ⇒ hiddenTokens.rawTokens
82  
-        case None               ⇒ hiddenPredecessors(ordinaryToken).rawTokens :+ ordinaryToken
83  
-      }
84  
-      for {
85  
-        token ← allTokens
86  
-        if isSelectableToken(token.tokenType)
87  
-        if token.range contains initialSelection
88  
-      } {
89  
-        if (token.isScalaDocComment && token.range == initialSelection)
90  
-          for {
91  
-            compilationUnit ← compilationUnitOpt
92  
-            expandedToAstRange ← appendAstNodeIfPossible(compilationUnit, token)
93  
-          } return Some(expandedToAstRange)
94  
-        if (token.length > length)
95  
-          return Some(token.range)
96  
-      }
97  
-    }
98  
-    None
99  
-  }
  77
+  /**
  78
+   * If a Scaladoc comment is exactly selected, expand to an associated class, method etc.
  79
+   */
  80
+  private def expandScaladocToAssociatedNode(initialSelection: Range): Option[Range] =
  81
+    for {
  82
+      scaladocComment ← allTokens.find { token ⇒ token.isScalaDocComment && token.range == initialSelection }
  83
+      associatedNode ← findAssociatedAstNode(scaladocComment)
  84
+      nodeRange ← associatedNode.rangeOpt // <-- should always be defined here, in fact
  85
+    } yield scaladocComment.range mergeWith nodeRange
100 86
 
101  
-  private def appendAstNodeIfPossible(node: AstNode, commentToken: Token): Option[Range] =
102  
-    node.firstTokenOption flatMap { firstToken ⇒
  87
+  /**
  88
+   * If the selection is a strict subrange of some token, expand to the entire token.
  89
+   */
  90
+  private def expandToToken(initialSelection: Range): Option[Range] =
  91
+    allTokens find { token ⇒
  92
+      isSelectableToken(token) && (token.range contains initialSelection) && initialSelection.length < token.length
  93
+    } map { _.range }
  94
+
  95
+  private def findAssociatedAstNode(scaladocCommentToken: Token): Option[AstNode] =
  96
+    compilationUnitOpt.flatMap { cu ⇒ findAssociatedAstNode(cu, scaladocCommentToken) }
  97
+
  98
+  private def findAssociatedAstNode(nodeToSearch: AstNode, scaladocCommentToken: Token): Option[AstNode] =
  99
+    nodeToSearch.firstTokenOption flatMap { firstToken ⇒
103 100
       val hiddenTokens = getPriorHiddenTokens(firstToken)
104  
-      if (hiddenTokens.rawTokens contains commentToken)
105  
-        Some(commentToken.range mergeWith node.rangeOpt.get)
  101
+      if (hiddenTokens.rawTokens contains scaladocCommentToken)
  102
+        Some(nodeToSearch)
106 103
       else {
107 104
         for {
108  
-          childNode ← node.immediateChildren
109  
-          result ← appendAstNodeIfPossible(childNode, commentToken)
  105
+          childNode ← nodeToSearch.immediateChildren
  106
+          result ← findAssociatedAstNode(childNode, scaladocCommentToken)
110 107
         } return Some(result)
111 108
         None
112 109
       }
113 110
     }
114 111
 
115  
-  private def isSelectableToken(tokenType: TokenType) = {
  112
+  private def isSelectableToken(token: Token) = {
  113
+    val tokenType = token.tokenType
116 114
     import tokenType._
117  
-    isLiteral || isKeyword || isComment || isId || (selectableXmls contains tokenType)
  115
+    isLiteral || isKeyword || isComment || isId || (selectableXmlTokens contains tokenType)
118 116
   }
119 117
 
120  
-  private def expandToEnclosingAst(node: AstNode, initialSelection: Range, enclosingNodes: List[AstNode]): Option[Range] =
121  
-    adjustedNodeRange(node) flatMap { nodeRange ⇒
122  
-      if (nodeRange contains initialSelection) {
123  
-        for {
124  
-          childNode ← node.immediateChildren
125  
-          descendantRange ← expandToEnclosingAst(childNode, initialSelection, enclosingNodes = node :: enclosingNodes)
126  
-        } return Some(descendantRange)
127  
-        if ((nodeRange contains initialSelection) && (nodeRange isLargerThan initialSelection) && isSelectableAst(node, enclosingNodes))
128  
-          Some(nodeRange)
129  
-        else
130  
-          None
131  
-      } else
132  
-        None
  118
+  /**
  119
+   * @return range of the node and any Scaladoc immediately before it
  120
+   */
  121
+  private def adjustedNodeRange(node: AstNode): Option[Range] =
  122
+    node.rangeOpt map { nodeRange ⇒
  123
+      val scaladocComments = getPriorHiddenTokens(node.firstToken).scalaDocComments
  124
+      scaladocComments.lastOption map { comment ⇒ nodeRange mergeWith comment.token.range } getOrElse nodeRange
133 125
     }
134 126
 
  127
+  /**
  128
+   * Attempt to find a suitable AST node to expand to which contains the given selection.
  129
+   *
  130
+   * @param enclosingNodes -- stack of nodes recording path to root compilation unit (useful for more context-aware
  131
+   *   decisions about whether to expand to a node or not).
  132
+   */
  133
+  private def expandToEnclosingAst(node: AstNode, initialSelection: Range, enclosingNodes: List[AstNode]): Option[Range] = {
  134
+
  135
+    val nodeRange = adjustedNodeRange(node).getOrElse { return None }
  136
+
  137
+    if (!nodeRange.contains(initialSelection)) { return None }
  138
+
  139
+    for {
  140
+      childNode ← node.immediateChildren
  141
+      descendantRange ← expandToEnclosingAst(childNode, initialSelection, enclosingNodes = node :: enclosingNodes)
  142
+    } return Some(descendantRange)
  143
+
  144
+    if (nodeRange.strictlyContains(initialSelection) && isSelectableAst(node :: enclosingNodes))
  145
+      Some(nodeRange)
  146
+    else
  147
+      None
  148
+
  149
+  }
  150
+
135 151
   private def getPredecessorNewline(token: Token): Option[HiddenTokens] =
136 152
     tokens.indexOf(token) match {
137 153
       case 0 ⇒ None
@@ -140,20 +156,12 @@ class AstSelector(source: String) {
140 156
 
141 157
   private def getPriorHiddenTokens(token: Token) = getPredecessorNewline(token) getOrElse hiddenPredecessors(token)
142 158
 
143  
-  private def isSelectableAst(node: AstNode, enclosingNodes: List[AstNode]) = {
144  
-    // println((node:: enclosingNodes) map (_.getClass.getSimpleName) mkString " ")
145  
-    (node :: enclosingNodes) match {
146  
-      case n1 :: n2 :: _ if n1.isInstanceOf[BlockExpr] && n2.isInstanceOf[MatchExpr] ⇒ false
147  
-
148  
-      // case n1 :: n2 :: n3 :: _ if n1.isInstanceOf[BlockExpr] && n2.isInstanceOf[Expr] && n3.isInstanceOf[ForExpr] ⇒ false
149  
-      // case n1 :: n2 :: _ if n1.isInstanceOf[Expr] && n2.isInstanceOf[ForExpr] ⇒ false
150  
-
151  
-      // case n1 :: n2 :: n3 :: _ if n1.isInstanceOf[BlockExpr] && n2.isInstanceOf[Expr] && n3.isInstanceOf[ExprFunBody] ⇒ false
152  
-      // case n1 :: n2 :: _ if n1.isInstanceOf[Expr] && n2.isInstanceOf[ExprFunBody] ⇒ false
153  
-
154  
-      case n1 :: n2 :: _ if n1.isInstanceOf[BlockExpr] && n2.isInstanceOf[ProcFunBody] ⇒ false
155  
-
156  
-      case _ ⇒ !(nonSelectableAstNodes contains node.getClass)
  159
+  private def isSelectableAst(nodeStack: List[AstNode]) =
  160
+    nodeStack match {
  161
+      case List(_: BlockExpr, _: MatchExpr, _*)   ⇒ false
  162
+      case List(_: BlockExpr, _: ProcFunBody, _*) ⇒ false
  163
+      case List(node, _*)                         ⇒ !(nonSelectableAstNodes contains node.getClass)
  164
+      case Nil                                    ⇒ false
157 165
     }
158  
-  }
159  
-}
  166
+
  167
+}
7  scalariform/src/main/scala/scalariform/utils/Range.scala
@@ -6,13 +6,16 @@ case class Range(offset: Int, length: Int) {
6 6
 
7 7
   def contains(other: Range) = other.offset >= offset && other.offset + other.length <= offset + length
8 8
 
  9
+  def strictlyContains(other: Range) = (this contains other) && this.length > other.length
  10
+
  11
+  /**
  12
+   * @return the smallest range that contains both this and other
  13
+   */
9 14
   def mergeWith(other: Range) = {
10 15
     val List(earliest, latest) = List(this, other) sortBy (_.offset)
11 16
     Range(earliest.offset, latest.offset - earliest.offset + latest.length)
12 17
   }
13 18
 
14  
-  def isLargerThan(other: Range) = length > other.length
15  
-
16 19
   def intersects(other: Range) =
17 20
     !(other.offset >= offset + length || other.offset + other.length - 1 < offset)
18 21
 
2  scalariform/src/main/scala/scalariform/utils/Utils.scala
@@ -6,6 +6,8 @@ import java.io.IOException
6 6
 
7 7
 object Utils {
8 8
 
  9
+  def when[T](b: Boolean)(x: => T): Option[T] = if (b) Some(x) else None
  10
+  
9 11
   def asInstanceOf[T](o: Any) = if (o.isInstanceOf[T]) Some(o.asInstanceOf[T]) else None
10 12
 
11 13
   def checkNotNull[T](item: T): T = { require(item != null); item }

0 notes on commit f46e687

Please sign in to comment.
Something went wrong with that request. Please try again.