use once_cell::sync::Lazy;
use regex::Regex;
use crate::cop::{Category, Cop, Severity};
use crate::offense::{Location, Offense};
use crate::source::SourceFile;
fn is_magic_comment(line: &str) -> bool {
let trimmed = line.trim();
trimmed.starts_with("# frozen_string_literal:")
|| trimmed.starts_with("# encoding:")
|| trimmed.starts_with("# coding:")
|| trimmed.starts_with("# warn_indent:")
}
pub struct LineLength;
impl Cop for LineLength {
fn name(&self) -> &str {
"Layout/LineLength"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks that lines do not exceed 120 characters"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
const MAX_LENGTH: usize = 120;
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if line.len() > MAX_LENGTH {
offenses.push(Offense::new(
self.name(),
format!("Line is too long. [{}/{}]", line.len(), MAX_LENGTH),
self.severity(),
Location::new(line_number, MAX_LENGTH + 1, line.len() - MAX_LENGTH),
));
}
}
offenses
}
}
pub struct EmptyComment;
impl Cop for EmptyComment {
fn name(&self) -> &str {
"Layout/EmptyComment"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for comments that contain only a # character"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
let trimmed = line.trim();
if trimmed == "#" {
if let Some(col) = line.find('#') {
offenses.push(Offense::new(
self.name(),
"Empty comment detected.",
self.severity(),
Location::new(line_number, col + 1, 1),
));
}
}
}
offenses
}
}
pub struct EmptyLines;
impl Cop for EmptyLines {
fn name(&self) -> &str {
"Layout/EmptyLines"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for multiple consecutive blank lines"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
let mut consecutive_blank = 0;
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if line.trim().is_empty() {
consecutive_blank += 1;
if consecutive_blank > 1 {
offenses.push(Offense::new(
self.name(),
"Multiple consecutive blank lines detected.",
self.severity(),
Location::new(line_number, 1, 1),
));
}
} else {
consecutive_blank = 0;
}
}
offenses
}
}
pub struct LeadingCommentSpace;
static LEADING_COMMENT_SPACE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"#[^ #\n]"#).unwrap());
impl Cop for LeadingCommentSpace {
fn name(&self) -> &str {
"Layout/LeadingCommentSpace"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing space after # in comments"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
let trimmed = line.trim();
if trimmed.starts_with("#!") || is_magic_comment(line) {
continue;
}
if let Some(mat) = LEADING_COMMENT_SPACE_RE.find(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) || line.chars().nth(col - 1) == Some('#') {
offenses.push(Offense::new(
self.name(),
"Missing space after #.",
self.severity(),
Location::new(line_number, col, 2),
));
}
}
}
offenses
}
}
pub struct ExtraSpacing;
static EXTRA_SPACING_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"[^ \t] +[^ \t]"#).unwrap());
impl Cop for ExtraSpacing {
fn name(&self) -> &str {
"Layout/ExtraSpacing"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for extra spacing between tokens"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
let trimmed_start = line.len() - line.trim_start().len();
let content = &line[trimmed_start..];
for mat in EXTRA_SPACING_RE.find_iter(content) {
let col = mat.start() + trimmed_start + 2; if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Extra spacing detected.",
self.severity(),
Location::new(line_number, col, mat.end() - mat.start() - 1),
));
}
}
}
offenses
}
}
pub struct SpaceAfterColon;
static SPACE_AFTER_COLON_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#":[^ \n]"#).unwrap());
impl Cop for SpaceAfterColon {
fn name(&self) -> &str {
"Layout/SpaceAfterColon"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing space after colon in hash literals"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_AFTER_COLON_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
let prev_char = if col > 1 { line.chars().nth(col - 2) } else { None };
if prev_char != Some(':') && prev_char != Some(' ') {
offenses.push(Offense::new(
self.name(),
"Space missing after colon.",
self.severity(),
Location::new(line_number, col, 2),
));
}
}
}
}
offenses
}
}
pub struct SpaceAfterMethodName;
static SPACE_AFTER_METHOD_NAME_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\bdef\s+\w+\s+\("#).unwrap());
impl Cop for SpaceAfterMethodName {
fn name(&self) -> &str {
"Layout/SpaceAfterMethodName"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for space between method name and opening parenthesis"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if let Some(mat) = SPACE_AFTER_METHOD_NAME_RE.find(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Do not put space between method name and opening parenthesis.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceAfterNot;
static SPACE_AFTER_NOT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"!\s+[^\s=]"#).unwrap());
impl Cop for SpaceAfterNot {
fn name(&self) -> &str {
"Layout/SpaceAfterNot"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for space after ! operator"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_AFTER_NOT_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Do not leave space after ! operator.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceAfterSemicolon;
static SPACE_AFTER_SEMICOLON_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#";[^ \n]"#).unwrap());
impl Cop for SpaceAfterSemicolon {
fn name(&self) -> &str {
"Layout/SpaceAfterSemicolon"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing space after semicolon"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_AFTER_SEMICOLON_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space missing after semicolon.",
self.severity(),
Location::new(line_number, col, 2),
));
}
}
}
offenses
}
}
pub struct SpaceBeforeComma;
static SPACE_BEFORE_COMMA_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\s,"#).unwrap());
impl Cop for SpaceBeforeComma {
fn name(&self) -> &str {
"Layout/SpaceBeforeComma"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for space before comma"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_BEFORE_COMMA_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space found before comma.",
self.severity(),
Location::new(line_number, col, 2),
));
}
}
}
offenses
}
}
pub struct SpaceBeforeComment;
static SPACE_BEFORE_COMMENT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"[^ \t]#"#).unwrap());
impl Cop for SpaceBeforeComment {
fn name(&self) -> &str {
"Layout/SpaceBeforeComment"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing space before inline comments"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if line.trim_start().starts_with('#') {
continue;
}
for mat in SPACE_BEFORE_COMMENT_RE.find_iter(line) {
let _col = mat.start() + 2; if let Some(hash_pos) = line[mat.start()..].find('#') {
let actual_col = mat.start() + hash_pos + 1;
if !source.in_string_or_comment(line_number, actual_col - 1) {
offenses.push(Offense::new(
self.name(),
"Missing space before inline comment.",
self.severity(),
Location::new(line_number, actual_col, 1),
));
}
}
}
}
offenses
}
}
pub struct SpaceBeforeSemicolon;
static SPACE_BEFORE_SEMICOLON_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\s;"#).unwrap());
impl Cop for SpaceBeforeSemicolon {
fn name(&self) -> &str {
"Layout/SpaceBeforeSemicolon"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for space before semicolon"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_BEFORE_SEMICOLON_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space found before semicolon.",
self.severity(),
Location::new(line_number, col, 2),
));
}
}
}
offenses
}
}
pub struct SpaceInsideArrayLiteralBrackets;
static SPACE_INSIDE_ARRAY_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\[\s+|\s+\]"#).unwrap());
impl Cop for SpaceInsideArrayLiteralBrackets {
fn name(&self) -> &str {
"Layout/SpaceInsideArrayLiteralBrackets"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for spaces inside array literal brackets"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_INSIDE_ARRAY_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space inside array brackets.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceInsideHashLiteralBraces;
static SPACE_INSIDE_HASH_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\{\s+|\s+\}"#).unwrap());
impl Cop for SpaceInsideHashLiteralBraces {
fn name(&self) -> &str {
"Layout/SpaceInsideHashLiteralBraces"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for spaces inside hash literal braces"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_INSIDE_HASH_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
if col > 1 && line.chars().nth(col - 2) == Some('#') {
continue;
}
offenses.push(Offense::new(
self.name(),
"Space inside hash braces.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceInsideRangeLiteral;
static SPACE_INSIDE_RANGE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\s\.\.|\.\.\.?\s"#).unwrap());
impl Cop for SpaceInsideRangeLiteral {
fn name(&self) -> &str {
"Layout/SpaceInsideRangeLiteral"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for spaces inside range literals"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_INSIDE_RANGE_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space inside range literal.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceInsideReferenceBrackets;
static SPACE_INSIDE_REF_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\w\[\s+|\s+\]"#).unwrap());
impl Cop for SpaceInsideReferenceBrackets {
fn name(&self) -> &str {
"Layout/SpaceInsideReferenceBrackets"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for spaces inside reference brackets"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_INSIDE_REF_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space inside reference brackets.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceInsideStringInterpolation;
static SPACE_INSIDE_INTERP_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"#\{\s+|\s+\}"#).unwrap());
impl Cop for SpaceInsideStringInterpolation {
fn name(&self) -> &str {
"Layout/SpaceInsideStringInterpolation"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for spaces inside string interpolation"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_INSIDE_INTERP_RE.find_iter(line) {
let col = mat.start() + 1;
if mat.as_str().starts_with("#{") {
offenses.push(Offense::new(
self.name(),
"Space inside string interpolation.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceAroundEqualsInParameterDefault;
static SPACE_AROUND_EQUALS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\w\s*=\s*\w"#).unwrap());
impl Cop for SpaceAroundEqualsInParameterDefault {
fn name(&self) -> &str {
"Layout/SpaceAroundEqualsInParameterDefault"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for spaces around = in parameter defaults"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if line.contains("def ") {
for mat in SPACE_AROUND_EQUALS_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
let text = mat.as_str();
if !text.contains(" = ") && (text.contains(" =") || text.contains("= ")) {
offenses.push(Offense::new(
self.name(),
"Use spaces around = in parameter default.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
}
}
offenses
}
}
pub struct SpaceAroundKeyword;
static SPACE_AROUND_KEYWORD_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"\b(if|unless|while|until|case|when)\w+"#).unwrap()
});
impl Cop for SpaceAroundKeyword {
fn name(&self) -> &str {
"Layout/SpaceAroundKeyword"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing space after keywords"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_AROUND_KEYWORD_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space missing after keyword.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceBeforeBlockBraces;
static SPACE_BEFORE_BLOCK_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\w\{"#).unwrap());
impl Cop for SpaceBeforeBlockBraces {
fn name(&self) -> &str {
"Layout/SpaceBeforeBlockBraces"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing space before block braces"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_BEFORE_BLOCK_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space missing before block brace.",
self.severity(),
Location::new(line_number, col, 2),
));
}
}
}
offenses
}
}
pub struct SpaceInsideBlockBraces;
impl Cop for SpaceInsideBlockBraces {
fn name(&self) -> &str {
"Layout/SpaceInsideBlockBraces"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for missing spaces inside block braces"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if let Some(pos) = line.find("{|") {
if !source.in_string_or_comment(line_number, pos + 1) {
offenses.push(Offense::new(
self.name(),
"Space missing inside block braces.",
self.severity(),
Location::new(line_number, pos + 1, 2),
));
}
}
if let Some(pos) = line.find("|}") {
if !source.in_string_or_comment(line_number, pos + 1) {
offenses.push(Offense::new(
self.name(),
"Space missing inside block braces.",
self.severity(),
Location::new(line_number, pos + 1, 2),
));
}
}
}
offenses
}
}
pub struct SpaceBeforeFirstArg;
static SPACE_BEFORE_FIRST_ARG_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"\w+ +\w+"#).unwrap()
});
impl Cop for SpaceBeforeFirstArg {
fn name(&self) -> &str {
"Layout/SpaceBeforeFirstArg"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for extra space before first argument"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_BEFORE_FIRST_ARG_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
if !line[..mat.start()].ends_with("def ") && !line[..mat.start()].ends_with("class ") {
offenses.push(Offense::new(
self.name(),
"Extra space before first argument.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
}
offenses
}
}
pub struct SpaceBeforeBrackets;
static SPACE_BEFORE_BRACKETS_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"\w\s+\["#).unwrap());
impl Cop for SpaceBeforeBrackets {
fn name(&self) -> &str {
"Layout/SpaceBeforeBrackets"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for space before brackets in array access"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_BEFORE_BRACKETS_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
offenses.push(Offense::new(
self.name(),
"Space found before brackets.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
offenses
}
}
pub struct SpaceInLambdaLiteral;
static SPACE_IN_LAMBDA_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"->\s*\("#).unwrap());
impl Cop for SpaceInLambdaLiteral {
fn name(&self) -> &str {
"Layout/SpaceInLambdaLiteral"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for space in lambda literal"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
for mat in SPACE_IN_LAMBDA_RE.find_iter(line) {
let col = mat.start() + 1;
if !source.in_string_or_comment(line_number, col) {
let text = mat.as_str();
if text.contains(" (") {
offenses.push(Offense::new(
self.name(),
"Unexpected space in lambda literal.",
self.severity(),
Location::new(line_number, col, mat.as_str().len()),
));
}
}
}
}
offenses
}
}
pub struct InitialIndentation;
impl Cop for InitialIndentation {
fn name(&self) -> &str {
"Layout/InitialIndentation"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks that first line has no indentation"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
if let Some(first_line) = source.line(1) {
if !first_line.is_empty() && first_line.starts_with(|c: char| c.is_whitespace()) {
if !first_line.trim_start().starts_with('#') {
offenses.push(Offense::new(
self.name(),
"Indentation on first line detected.",
self.severity(),
Location::new(1, 1, first_line.len() - first_line.trim_start().len()),
));
}
}
}
offenses
}
}
pub struct CommentIndentation;
impl Cop for CommentIndentation {
fn name(&self) -> &str {
"Layout/CommentIndentation"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks that comments are aligned with surrounding code"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
let trimmed = line.trim_start();
if trimmed.starts_with('#') && !trimmed.starts_with("#!") {
if line_num > 1 {
for prev in (1..line_num).rev() {
if let Some(prev_line) = source.line(prev) {
if !prev_line.trim().is_empty() && !prev_line.trim_start().starts_with('#') {
let prev_indent = prev_line.len() - prev_line.trim_start().len();
let curr_indent = line.len() - trimmed.len();
if curr_indent != prev_indent {
offenses.push(Offense::new(
self.name(),
"Comment indentation incorrect.",
self.severity(),
Location::new(line_num, 1, curr_indent),
));
}
break;
}
}
}
}
}
}
}
offenses
}
}
pub struct IndentationConsistency;
impl Cop for IndentationConsistency {
fn name(&self) -> &str {
"Layout/IndentationConsistency"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for consistent indentation style"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
let mut has_tabs = false;
let mut has_spaces = false;
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
if line.starts_with('\t') {
has_tabs = true;
} else if line.starts_with(' ') {
has_spaces = true;
}
if has_tabs && has_spaces {
offenses.push(Offense::new(
self.name(),
"Mixed tabs and spaces in indentation.",
self.severity(),
Location::new(line_number, 1, 1),
));
break;
}
}
offenses
}
}
pub struct DotPosition;
impl Cop for DotPosition {
fn name(&self) -> &str {
"Layout/DotPosition"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks dot position in method chains"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
let trimmed = line.trim_end();
if trimmed.ends_with('.') && !source.in_string_or_comment(line_number, trimmed.len()) {
offenses.push(Offense::new(
self.name(),
"Place dot at beginning of next line in method chain.",
self.severity(),
Location::new(line_number, trimmed.len(), 1),
));
}
}
offenses
}
}
pub struct ConditionPosition;
impl Cop for ConditionPosition {
fn name(&self) -> &str {
"Layout/ConditionPosition"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks that conditions are on same line as if/while"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
let line_number = line_num + 1;
let trimmed = line.trim();
if trimmed == "if" || trimmed == "unless" || trimmed == "while" || trimmed == "until" {
offenses.push(Offense::new(
self.name(),
"Place condition on same line as keyword.",
self.severity(),
Location::new(line_number, 1, trimmed.len()),
));
}
}
offenses
}
}
pub struct EmptyLineAfterMagicComment;
impl Cop for EmptyLineAfterMagicComment {
fn name(&self) -> &str {
"Layout/EmptyLineAfterMagicComment"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for blank line after magic comments"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
if is_magic_comment(line) {
if let Some(next_line) = source.line(line_num + 1) {
if !next_line.trim().is_empty() && !is_magic_comment(next_line) {
offenses.push(Offense::new(
self.name(),
"Add empty line after magic comment.",
self.severity(),
Location::new(line_num + 1, 1, 1),
));
}
}
}
}
}
offenses
}
}
pub struct EmptyLineAfterGuardClause;
static GUARD_CLAUSE_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"^\s*(return|raise|break|next)\s+(if|unless)\s+"#).unwrap()
});
impl Cop for EmptyLineAfterGuardClause {
fn name(&self) -> &str {
"Layout/EmptyLineAfterGuardClause"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for blank line after guard clauses"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..source.line_count() {
if let Some(line) = source.line(line_num) {
if GUARD_CLAUSE_RE.is_match(line) {
if let Some(next_line) = source.line(line_num + 1) {
if !next_line.trim().is_empty() {
offenses.push(Offense::new(
self.name(),
"Add empty line after guard clause.",
self.severity(),
Location::new(line_num + 1, 1, 1),
));
}
}
}
}
}
offenses
}
}
pub struct EmptyLinesAroundClassBody;
impl Cop for EmptyLinesAroundClassBody {
fn name(&self) -> &str {
"Layout/EmptyLinesAroundClassBody"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for empty lines at start/end of class body"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
if line.trim_start().starts_with("class ") {
if let Some(next) = source.line(line_num + 1) {
if next.trim().is_empty() {
offenses.push(Offense::new(
self.name(),
"Empty line at beginning of class body.",
self.severity(),
Location::new(line_num + 1, 1, 1),
));
}
}
}
}
}
offenses
}
}
pub struct EmptyLinesAroundModuleBody;
impl Cop for EmptyLinesAroundModuleBody {
fn name(&self) -> &str {
"Layout/EmptyLinesAroundModuleBody"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for empty lines at start/end of module body"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
if line.trim_start().starts_with("module ") {
if let Some(next) = source.line(line_num + 1) {
if next.trim().is_empty() {
offenses.push(Offense::new(
self.name(),
"Empty line at beginning of module body.",
self.severity(),
Location::new(line_num + 1, 1, 1),
));
}
}
}
}
}
offenses
}
}
pub struct EmptyLinesAroundMethodBody;
impl Cop for EmptyLinesAroundMethodBody {
fn name(&self) -> &str {
"Layout/EmptyLinesAroundMethodBody"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for empty lines at start/end of method body"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
if line.trim_start().starts_with("def ") {
if let Some(next) = source.line(line_num + 1) {
if next.trim().is_empty() {
offenses.push(Offense::new(
self.name(),
"Empty line at beginning of method body.",
self.severity(),
Location::new(line_num + 1, 1, 1),
));
}
}
}
}
}
offenses
}
}
pub struct EmptyLinesAroundBlockBody;
impl Cop for EmptyLinesAroundBlockBody {
fn name(&self) -> &str {
"Layout/EmptyLinesAroundBlockBody"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for empty lines at start/end of block body"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
if line.contains(" do") || line.contains(" do|") {
if let Some(next) = source.line(line_num + 1) {
if next.trim().is_empty() {
offenses.push(Offense::new(
self.name(),
"Empty line at beginning of block body.",
self.severity(),
Location::new(line_num + 1, 1, 1),
));
}
}
}
}
}
offenses
}
}
pub struct EmptyLinesAroundAccessModifier;
impl Cop for EmptyLinesAroundAccessModifier {
fn name(&self) -> &str {
"Layout/EmptyLinesAroundAccessModifier"
}
fn category(&self) -> Category {
Category::Layout
}
fn severity(&self) -> Severity {
Severity::Convention
}
fn description(&self) -> &str {
"Checks for blank lines around access modifiers"
}
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for line_num in 1..=source.line_count() {
if let Some(line) = source.line(line_num) {
let trimmed = line.trim();
if trimmed == "private" || trimmed == "protected" || trimmed == "public" {
if line_num > 1 {
if let Some(prev) = source.line(line_num - 1) {
if !prev.trim().is_empty() {
offenses.push(Offense::new(
self.name(),
"Add empty line before access modifier.",
self.severity(),
Location::new(line_num, 1, trimmed.len()),
));
}
}
}
}
}
}
offenses
}
}
pub struct SingleLineBlockChain;
impl Cop for SingleLineBlockChain {
fn name(&self) -> &str { "Layout/SingleLineBlockChain" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for chaining after single-line blocks" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct BlockEndNewline;
impl Cop for BlockEndNewline {
fn name(&self) -> &str { "Layout/BlockEndNewline" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks that end of block is on own line" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct CaseIndentation;
impl Cop for CaseIndentation {
fn name(&self) -> &str { "Layout/CaseIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of when in case statements" }
fn check(&self, source: &SourceFile) -> Vec<Offense> {
let mut offenses = Vec::new();
for (line_num, line) in source.lines.iter().enumerate() {
if line.trim_start().starts_with("when ") {
let indent = line.len() - line.trim_start().len();
if indent == 0 {
offenses.push(Offense::new(
self.name(),
"Indent when as deep as case.",
self.severity(),
Location::new(line_num + 1, 1, 4),
));
}
}
}
offenses
}
}
pub struct EndAlignment;
impl Cop for EndAlignment {
fn name(&self) -> &str { "Layout/EndAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of end with opening keyword" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct DefEndAlignment;
impl Cop for DefEndAlignment {
fn name(&self) -> &str { "Layout/DefEndAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of method end with def" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ElseAlignment;
impl Cop for ElseAlignment {
fn name(&self) -> &str { "Layout/ElseAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of else with if" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct RescueEnsureAlignment;
impl Cop for RescueEnsureAlignment {
fn name(&self) -> &str { "Layout/RescueEnsureAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of rescue/ensure with begin" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct AccessModifierIndentation;
impl Cop for AccessModifierIndentation {
fn name(&self) -> &str { "Layout/AccessModifierIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of access modifiers" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ClosingParenthesisIndentation;
impl Cop for ClosingParenthesisIndentation {
fn name(&self) -> &str { "Layout/ClosingParenthesisIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of closing parenthesis" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineMethodCallIndentation;
impl Cop for MultilineMethodCallIndentation {
fn name(&self) -> &str { "Layout/MultilineMethodCallIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of multiline method calls" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineOperationIndentation;
impl Cop for MultilineOperationIndentation {
fn name(&self) -> &str { "Layout/MultilineOperationIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of multiline operations" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ArgumentAlignment;
impl Cop for ArgumentAlignment {
fn name(&self) -> &str { "Layout/ArgumentAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of method arguments" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ArrayAlignment;
impl Cop for ArrayAlignment {
fn name(&self) -> &str { "Layout/ArrayAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of array elements" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct HashAlignment;
impl Cop for HashAlignment {
fn name(&self) -> &str { "Layout/HashAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of hash pairs" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ParameterAlignment;
impl Cop for ParameterAlignment {
fn name(&self) -> &str { "Layout/ParameterAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of method parameters" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct AssignmentIndentation;
impl Cop for AssignmentIndentation {
fn name(&self) -> &str { "Layout/AssignmentIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of assignment RHS" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstArgumentIndentation;
impl Cop for FirstArgumentIndentation {
fn name(&self) -> &str { "Layout/FirstArgumentIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of first argument" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstArrayElementIndentation;
impl Cop for FirstArrayElementIndentation {
fn name(&self) -> &str { "Layout/FirstArrayElementIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of first array element" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstHashElementIndentation;
impl Cop for FirstHashElementIndentation {
fn name(&self) -> &str { "Layout/FirstHashElementIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of first hash element" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct HeredocIndentation;
impl Cop for HeredocIndentation {
fn name(&self) -> &str { "Layout/HeredocIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of heredoc content" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct LineContinuationSpacing;
impl Cop for LineContinuationSpacing {
fn name(&self) -> &str { "Layout/LineContinuationSpacing" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks spacing around line continuation" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct LineContinuationLeadingSpace;
impl Cop for LineContinuationLeadingSpace {
fn name(&self) -> &str { "Layout/LineContinuationLeadingSpace" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks leading space after line continuation" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineArrayBraceLayout;
impl Cop for MultilineArrayBraceLayout {
fn name(&self) -> &str { "Layout/MultilineArrayBraceLayout" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks bracket placement for multiline arrays" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineHashBraceLayout;
impl Cop for MultilineHashBraceLayout {
fn name(&self) -> &str { "Layout/MultilineHashBraceLayout" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks brace placement for multiline hashes" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineBlockLayout;
impl Cop for MultilineBlockLayout {
fn name(&self) -> &str { "Layout/MultilineBlockLayout" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks layout of multiline blocks" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineMethodCallBraceLayout;
impl Cop for MultilineMethodCallBraceLayout {
fn name(&self) -> &str { "Layout/MultilineMethodCallBraceLayout" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks brace layout for method calls" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineArrayLineBreaks;
impl Cop for MultilineArrayLineBreaks {
fn name(&self) -> &str { "Layout/MultilineArrayLineBreaks" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks that each array element is on own line" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineHashKeyLineBreaks;
impl Cop for MultilineHashKeyLineBreaks {
fn name(&self) -> &str { "Layout/MultilineHashKeyLineBreaks" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks that each hash key is on own line" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineMethodArgumentLineBreaks;
impl Cop for MultilineMethodArgumentLineBreaks {
fn name(&self) -> &str { "Layout/MultilineMethodArgumentLineBreaks" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks that each argument is on own line" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineMethodParameterLineBreaks;
impl Cop for MultilineMethodParameterLineBreaks {
fn name(&self) -> &str { "Layout/MultilineMethodParameterLineBreaks" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks that each parameter is on own line" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstMethodArgumentLineBreak;
impl Cop for FirstMethodArgumentLineBreak {
fn name(&self) -> &str { "Layout/FirstMethodArgumentLineBreak" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks line break after opening parenthesis" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstMethodParameterLineBreak;
impl Cop for FirstMethodParameterLineBreak {
fn name(&self) -> &str { "Layout/FirstMethodParameterLineBreak" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks line break for first parameter" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstArrayElementLineBreak;
impl Cop for FirstArrayElementLineBreak {
fn name(&self) -> &str { "Layout/FirstArrayElementLineBreak" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks line break for first array element" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstHashElementLineBreak;
impl Cop for FirstHashElementLineBreak {
fn name(&self) -> &str { "Layout/FirstHashElementLineBreak" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks line break for first hash element" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineAssignmentLayout;
impl Cop for MultilineAssignmentLayout {
fn name(&self) -> &str { "Layout/MultilineAssignmentLayout" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks layout of multiline assignments" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct MultilineMethodDefinitionBraceLayout;
impl Cop for MultilineMethodDefinitionBraceLayout {
fn name(&self) -> &str { "Layout/MultilineMethodDefinitionBraceLayout" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks brace layout in method definitions" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct RedundantLineBreak;
impl Cop for RedundantLineBreak {
fn name(&self) -> &str { "Layout/RedundantLineBreak" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for unnecessary line breaks" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ClassStructure;
impl Cop for ClassStructure {
fn name(&self) -> &str { "Layout/ClassStructure" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks class structure ordering" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct LineEndStringConcatenationIndentation;
impl Cop for LineEndStringConcatenationIndentation {
fn name(&self) -> &str { "Layout/LineEndStringConcatenationIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of string concatenation" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ClosingHeredocIndentation;
impl Cop for ClosingHeredocIndentation {
fn name(&self) -> &str { "Layout/ClosingHeredocIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of heredoc closing delimiter" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct BeginEndAlignment;
impl Cop for BeginEndAlignment {
fn name(&self) -> &str { "Layout/BeginEndAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks alignment of begin/end" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct BlockAlignment;
impl Cop for BlockAlignment {
fn name(&self) -> &str { "Layout/BlockAlignment" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks block alignment with variable" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct EmptyLineAfterMultilineCondition;
impl Cop for EmptyLineAfterMultilineCondition {
fn name(&self) -> &str { "Layout/EmptyLineAfterMultilineCondition" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for blank line after multiline condition" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct EmptyLinesAfterModuleInclusion;
impl Cop for EmptyLinesAfterModuleInclusion {
fn name(&self) -> &str { "Layout/EmptyLinesAfterModuleInclusion" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for blank lines after include/extend" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct EmptyLinesAroundAttributeAccessor;
impl Cop for EmptyLinesAroundAttributeAccessor {
fn name(&self) -> &str { "Layout/EmptyLinesAroundAttributeAccessor" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for blank lines around attr_accessor" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct EmptyLinesAroundBeginBody;
impl Cop for EmptyLinesAroundBeginBody {
fn name(&self) -> &str { "Layout/EmptyLinesAroundBeginBody" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for blank lines in begin body" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct EmptyLinesAroundExceptionHandlingKeywords;
impl Cop for EmptyLinesAroundExceptionHandlingKeywords {
fn name(&self) -> &str { "Layout/EmptyLinesAroundExceptionHandlingKeywords" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for blank lines around rescue/ensure" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct EmptyLinesAroundArguments;
impl Cop for EmptyLinesAroundArguments {
fn name(&self) -> &str { "Layout/EmptyLinesAroundArguments" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for blank lines in argument lists" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct SpaceAroundMethodCallOperator;
impl Cop for SpaceAroundMethodCallOperator {
fn name(&self) -> &str { "Layout/SpaceAroundMethodCallOperator" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for space around . and ::" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct SpaceInsideArrayPercentLiteral;
impl Cop for SpaceInsideArrayPercentLiteral {
fn name(&self) -> &str { "Layout/SpaceInsideArrayPercentLiteral" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for spaces in %w[] / %i[]" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct SpaceInsidePercentLiteralDelimiters;
impl Cop for SpaceInsidePercentLiteralDelimiters {
fn name(&self) -> &str { "Layout/SpaceInsidePercentLiteralDelimiters" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks for spaces in %() literals" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct HeredocArgumentClosingParenthesis;
impl Cop for HeredocArgumentClosingParenthesis {
fn name(&self) -> &str { "Layout/HeredocArgumentClosingParenthesis" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks closing paren after heredoc argument" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct FirstParameterIndentation;
impl Cop for FirstParameterIndentation {
fn name(&self) -> &str { "Layout/FirstParameterIndentation" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Checks indentation of first parameter" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
pub struct ErrorA;
impl Cop for ErrorA {
fn name(&self) -> &str { "Layout/ErrorA" }
fn category(&self) -> Category { Category::Layout }
fn severity(&self) -> Severity { Severity::Convention }
fn description(&self) -> &str { "Internal error cop" }
fn check(&self, _source: &SourceFile) -> Vec<Offense> { Vec::new() }
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn test_source(content: &str) -> SourceFile {
SourceFile::from_string(PathBuf::from("test.rb"), content.to_string())
}
#[test]
fn test_line_length_pass() {
let source = test_source("x = 1\n");
let cop = LineLength;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_line_length_fail() {
let long_line = "x".repeat(121);
let source = test_source(&long_line);
let cop = LineLength;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_empty_comment_pass() {
let source = test_source("# This is a comment\n");
let cop = EmptyComment;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_empty_comment_fail() {
let source = test_source("#\n");
let cop = EmptyComment;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_empty_lines_pass() {
let source = test_source("x = 1\n\ny = 2\n");
let cop = EmptyLines;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_empty_lines_fail() {
let source = test_source("x = 1\n\n\ny = 2\n");
let cop = EmptyLines;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_leading_comment_space_pass() {
let source = test_source("# Good comment\n");
let cop = LeadingCommentSpace;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_leading_comment_space_fail() {
let source = test_source("#Bad comment\n");
let cop = LeadingCommentSpace;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_extra_spacing_pass() {
let source = test_source("x = 1 + 2\n");
let cop = ExtraSpacing;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_extra_spacing_fail() {
let source = test_source("x = 1 + 2\n");
let cop = ExtraSpacing;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_after_colon_pass() {
let source = test_source("{a: 1}\n");
let cop = SpaceAfterColon;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_after_colon_fail() {
let source = test_source("{a:1}\n");
let cop = SpaceAfterColon;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_after_method_name_pass() {
let source = test_source("def foo(x)\nend\n");
let cop = SpaceAfterMethodName;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_after_method_name_fail() {
let source = test_source("def foo (x)\nend\n");
let cop = SpaceAfterMethodName;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_after_not_pass() {
let source = test_source("!x\n");
let cop = SpaceAfterNot;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_after_not_fail() {
let source = test_source("! x\n");
let cop = SpaceAfterNot;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_after_semicolon_pass() {
let source = test_source("x = 1; y = 2\n");
let cop = SpaceAfterSemicolon;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_after_semicolon_fail() {
let source = test_source("x = 1;y = 2\n");
let cop = SpaceAfterSemicolon;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_before_comma_pass() {
let source = test_source("[1, 2, 3]\n");
let cop = SpaceBeforeComma;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_before_comma_fail() {
let source = test_source("[1 , 2]\n");
let cop = SpaceBeforeComma;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_before_comment_pass() {
let source = test_source("x = 1 # comment\n");
let cop = SpaceBeforeComment;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_before_comment_fail() {
let source = test_source("x = 1# comment\n");
let cop = SpaceBeforeComment;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_before_semicolon_pass() {
let source = test_source("x = 1; y = 2\n");
let cop = SpaceBeforeSemicolon;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_before_semicolon_fail() {
let source = test_source("x = 1 ;\n");
let cop = SpaceBeforeSemicolon;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_space_inside_array_pass() {
let source = test_source("[1, 2, 3]\n");
let cop = SpaceInsideArrayLiteralBrackets;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_inside_array_fail() {
let source = test_source("[ 1, 2 ]\n");
let cop = SpaceInsideArrayLiteralBrackets;
assert!(!cop.check(&source).is_empty());
}
#[test]
fn test_space_inside_hash_pass() {
let source = test_source("{a: 1}\n");
let cop = SpaceInsideHashLiteralBraces;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_inside_hash_fail() {
let source = test_source("{ a: 1 }\n");
let cop = SpaceInsideHashLiteralBraces;
assert!(!cop.check(&source).is_empty());
}
#[test]
fn test_space_inside_range_pass() {
let source = test_source("1..10\n");
let cop = SpaceInsideRangeLiteral;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_space_inside_range_fail() {
let source = test_source("1 .. 10\n");
let cop = SpaceInsideRangeLiteral;
assert!(!cop.check(&source).is_empty());
}
#[test]
fn test_initial_indentation_pass() {
let source = test_source("x = 1\n");
let cop = InitialIndentation;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_initial_indentation_fail() {
let source = test_source(" x = 1\n");
let cop = InitialIndentation;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_indentation_consistency_pass() {
let source = test_source(" x = 1\n y = 2\n");
let cop = IndentationConsistency;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_indentation_consistency_fail() {
let source = test_source(" x = 1\n\ty = 2\n");
let cop = IndentationConsistency;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_dot_position_pass() {
let source = test_source("foo\n .bar\n");
let cop = DotPosition;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_dot_position_fail() {
let source = test_source("foo.\n bar\n");
let cop = DotPosition;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_condition_position_pass() {
let source = test_source("if x > 1\nend\n");
let cop = ConditionPosition;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_condition_position_fail() {
let source = test_source("if\n x > 1\nend\n");
let cop = ConditionPosition;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_empty_line_after_magic_comment_pass() {
let source = test_source("# frozen_string_literal: true\n\nx = 1\n");
let cop = EmptyLineAfterMagicComment;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_empty_line_after_magic_comment_fail() {
let source = test_source("# frozen_string_literal: true\nx = 1\n");
let cop = EmptyLineAfterMagicComment;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_empty_line_after_guard_clause_pass() {
let source = test_source("return if x\n\ny = 1\n");
let cop = EmptyLineAfterGuardClause;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_empty_line_after_guard_clause_fail() {
let source = test_source("return if x\ny = 1\n");
let cop = EmptyLineAfterGuardClause;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_case_indentation_pass() {
let source = test_source("case x\n when 1\nend\n");
let cop = CaseIndentation;
assert_eq!(cop.check(&source).len(), 0);
}
#[test]
fn test_case_indentation_fail() {
let source = test_source("when 1\n");
let cop = CaseIndentation;
assert_eq!(cop.check(&source).len(), 1);
}
#[test]
fn test_simplified_cops_dont_crash() {
let source = test_source("x = 1\n");
assert_eq!(EndAlignment.check(&source).len(), 0);
assert_eq!(DefEndAlignment.check(&source).len(), 0);
assert_eq!(ElseAlignment.check(&source).len(), 0);
assert_eq!(RescueEnsureAlignment.check(&source).len(), 0);
assert_eq!(AccessModifierIndentation.check(&source).len(), 0);
assert_eq!(ArgumentAlignment.check(&source).len(), 0);
assert_eq!(ArrayAlignment.check(&source).len(), 0);
assert_eq!(HashAlignment.check(&source).len(), 0);
assert_eq!(ErrorA.check(&source).len(), 0);
}
}