All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalastyle.file.IndentationChecker.scala Maven / Gradle / Ivy

// Copyright (C) 2011-2012 the original author or authors.
// See the LICENCE.txt file distributed with this work for additional
// information regarding copyright ownership.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.scalastyle.file

import org.scalastyle.FileChecker
import org.scalastyle.LineError
import org.scalastyle.Line
import org.scalastyle.Lines
import org.scalastyle.ScalastyleError

object NormalizedLine {
  def normalize(lines: Lines, tabSize: Int): Array[NormalizedLine] =
    lines.lines.zipWithIndex map {
      case (line, index) => NormalizedLine(index + 1, line, tabSize)
    }
}

case class NormalizedLine(lineNumber: Int, line: Line, tabSize: Int) {
  lazy val normalizedText = replaceTabs(line.text, tabSize)
  lazy val length = normalizedText.length
  lazy val body = normalizedText dropWhile { _.isWhitespace }
  lazy val isBlank = { body.length == 0 }
  lazy val indentDepth = normalizedText prefixLength { _ == ' ' }

  def mkError(args: List[String] = Nil): LineError = LineError(lineNumber, args)

  // generates a string of spaces equal to the width of the tab
  private def spaces(column: Int, tabSize: Int): String = {
    val m = column % tabSize
    String.format("%" + (tabSize - m) + "s", " ")
  }

  // replaces tabs with spaces equal to the width of the tab
  private def replaceTabs(s: String, tabSize: Int): String = {
    val sb = new StringBuilder(s)
    val len = sb.length
    var i = 0;

    while (i < len) {
      if (sb.charAt(i) == '\t') {
        sb.replace(i, i + 1, spaces(i, tabSize))
      }
      i += 1
    }

    if (sb.endsWith("\r")) {
      sb.setLength(sb.length-1);
    }

    sb.toString
  }
}

class IndentationChecker extends FileChecker {
  val DefaultTabSize = 2
  val DefaultClassParamTabSize = 4
  val errorKey = "indentation"

  private def multiLineComment(line: NormalizedLine) = line.body.startsWith("*")

  private def startsParamList(line: NormalizedLine) = line.body.matches(""".*(class|object|trait) .*\([^\)]*""")

  private def startsMethodDef(line: NormalizedLine) = line.body.matches(""".*def .*\([^\)]*""")

  // in multiline comments the last leading space is not part of the indent
  private def isTabAlligned(line: NormalizedLine): Boolean =
    (line.indentDepth % line.tabSize) == (if (multiLineComment(line)) 1 else 0)

  private def isSingleIndent(line: NormalizedLine, prior: NormalizedLine): Boolean =
    (line.indentDepth - prior.indentDepth) > line.tabSize

  private def verifyTabStop(lines: Seq[NormalizedLine]) =
    for { line <- lines if !isTabAlligned(line) } yield line.mkError()

  /**
   * Verfiy single indent EXCLUDING class and method parameter lists
   */
  private def verifySingleIndent(lines: Seq[NormalizedLine]) = {
    def isInvalid(l1: NormalizedLine, l2: NormalizedLine): Boolean = {
      isSingleIndent(l2, l1) && !startsParamList(l1) && !startsMethodDef(l1)
    }

    for { Seq(l1, l2) <- lines.sliding(2) if isInvalid(l1, l2) } yield l2.mkError()
  }

  /**
   * Verify parameter indentation in class/object/trait parameter lists
   */
  private def verifyClassIndent(lines: Seq[NormalizedLine], classParamIndentSize: Int) = {
    def isInvalid(l1: NormalizedLine, l2: NormalizedLine): Boolean = {
      if (startsParamList(l1)) {
        (l2.indentDepth - l1.indentDepth) != classParamIndentSize
      } else {
        false
      }
    }

    for { Seq(l1, l2) <- lines.sliding(2) if isInvalid(l1, l2) } yield l2.mkError()
  }

  /**
   * Verify parameter indentation in method parameter lists
   */
  private def verifyMethodIndent(lines: Seq[NormalizedLine], methodParamIndentSize: Int) = {
    def isInvalid(l1: NormalizedLine, l2: NormalizedLine): Boolean = {
      if (startsMethodDef(l1)) {
        (l2.indentDepth - l1.indentDepth) != methodParamIndentSize
      } else {
        false
      }
    }

    for { Seq(l1, l2) <- lines.sliding(2) if isInvalid(l1, l2) } yield l2.mkError()
  }

  def verify(lines: Lines): List[ScalastyleError] = {
    val tabSize = getInt("tabSize", DefaultTabSize)
    val classParamIndentSize = getInt("classParamIndentSize", DefaultClassParamTabSize)
    val methodParamIndentSize = getInt("methodParamIndentSize", tabSize)

    val normalizedLines = NormalizedLine.normalize(lines, tabSize) filterNot { _.isBlank }

    val tabErrors = verifyTabStop(normalizedLines)
    val indentErrors = verifySingleIndent(normalizedLines)
    val classParamListErrors = verifyClassIndent(normalizedLines, classParamIndentSize)
    val methodParamListErrors = verifyMethodIndent(normalizedLines, methodParamIndentSize)

    (tabErrors ++ indentErrors ++ classParamListErrors ++ methodParamListErrors).toList
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy