Merge branch 'master' into Sija/release-1.4.0

This commit is contained in:
Vitalii Elenhaupt 2023-01-22 18:05:21 +02:00
commit 74407cc8f5
No known key found for this signature in database
GPG key ID: CD0BF17825928BC0
85 changed files with 848 additions and 1105 deletions

7
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,7 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily

74
.github/workflows/cd.yml vendored Normal file
View file

@ -0,0 +1,74 @@
name: Docker image build and deploy
on:
push:
branches: [master]
release:
types: [published]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into ${{ env.REGISTRY }} registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=ref,event=branch
type=ref,event=pr
type=sha,format=long
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
# Build and push Docker image with Buildx
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v3
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: |
linux/amd64
linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View file

@ -2,7 +2,9 @@ name: CI
on: on:
push: push:
branches: [master]
pull_request: pull_request:
types: [opened, synchronize, reopened]
schedule: schedule:
- cron: "0 3 * * 1" # Every monday at 3 AM - cron: "0 3 * * 1" # Every monday at 3 AM
@ -17,20 +19,21 @@ jobs:
steps: steps:
- name: Install Crystal - name: Install Crystal
uses: oprypin/install-crystal@v1 uses: crystal-lang/install-crystal@v1
with: with:
crystal: ${{ matrix.crystal }} crystal: ${{ matrix.crystal }}
- name: Download source - name: Download source
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Install dependencies - name: Install dependencies
run: shards install run: shards install
env:
SHARDS_OPTS: --ignore-crystal-version
- name: Run specs - name: Run specs
run: make test run: crystal spec
- name: Check formatting - name: Build ameba binary
run: crystal tool format --check run: shards build -Dpreview_mt
- name: Run ameba linter
run: bin/ameba --all

View file

@ -4,51 +4,32 @@ on:
push: push:
branches: [master] branches: [master]
permissions:
contents: write
jobs: jobs:
build: build-and-deploy:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Inject slug/short variables - name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x uses: rlespinasse/github-slug-action@v4
- name: Install Crystal - name: Install Crystal
uses: oprypin/install-crystal@v1 uses: crystal-lang/install-crystal@v1
- name: Download source - name: Download source
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
persist-credentials: false
- name: Install dependencies - name: Install dependencies
run: shards install run: shards install
env:
SHARDS_OPTS: --ignore-crystal-version
- name: Build docs - name: Build docs
run: | run: crystal docs --project-version="${{ env.GITHUB_REF_SLUG }}" --source-refname="${{ env.GITHUB_SHA_SHORT }}"
sed -i -e 's:<.*>::g' README.md
crystal docs --project-version="${{ env.GITHUB_REF_SLUG }}" --source-refname="${{ env.GITHUB_SHA_SHORT }}"
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: docs
path: docs
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: docs
path: docs
- name: Deploy docs 🚀 - name: Deploy docs 🚀
uses: JamesIves/github-pages-deploy-action@3.7.1 uses: JamesIves/github-pages-deploy-action@v4
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} branch: gh-pages
BRANCH: gh-pages folder: docs
FOLDER: docs clean: true
CLEAN: true

View file

@ -1,12 +1,12 @@
FROM alpine:edge as builder FROM alpine:edge as builder
RUN apk add --update crystal shards openssl-dev yaml-dev musl-dev make RUN apk add --update crystal shards yaml-dev musl-dev make
RUN mkdir /ameba RUN mkdir /ameba
WORKDIR /ameba WORKDIR /ameba
COPY . /ameba/ COPY . /ameba/
RUN make clean && make RUN make clean && make
FROM alpine:latest FROM alpine:latest
RUN apk add --update openssl yaml pcre gc libevent libgcc RUN apk add --update yaml pcre gc libevent libgcc
RUN mkdir /src RUN mkdir /src
WORKDIR /src WORKDIR /src
COPY --from=builder /ameba/bin/ameba /usr/bin/ COPY --from=builder /ameba/bin/ameba /usr/bin/

View file

@ -4,19 +4,35 @@ PREFIX ?= /usr/local
SHARD_BIN ?= ../../bin SHARD_BIN ?= ../../bin
CRFLAGS ?= -Dpreview_mt CRFLAGS ?= -Dpreview_mt
build: bin/ameba .PHONY: build
bin/ameba: build:
$(SHARDS_BIN) build $(CRFLAGS) $(SHARDS_BIN) build $(CRFLAGS)
.PHONY: lint
lint: build
./bin/ameba --all
.PHONY: spec
spec:
$(CRYSTAL_BIN) spec
.PHONY: clean
clean: clean:
rm -f ./bin/ameba ./bin/ameba.dwarf rm -f ./bin/ameba ./bin/ameba.dwarf
.PHONY: install
install: build install: build
mkdir -p $(PREFIX)/bin mkdir -p $(PREFIX)/bin
cp ./bin/ameba $(PREFIX)/bin cp ./bin/ameba $(PREFIX)/bin
.PHONY: bin
bin: build bin: build
mkdir -p $(SHARD_BIN) mkdir -p $(SHARD_BIN)
cp ./bin/ameba $(SHARD_BIN) cp ./bin/ameba $(SHARD_BIN)
.PHONY: run_file
run_file: run_file:
cp -n ./bin/ameba.cr $(SHARD_BIN) || true cp -n ./bin/ameba.cr $(SHARD_BIN) || true
test: build
$(CRYSTAL_BIN) spec .PHONY: test
./bin/ameba --all test: spec lint

View file

@ -162,16 +162,16 @@ $ brew install ameba
Build the image: Build the image:
```sh ```sh
$ docker build -t crystal-ameba/ameba . $ docker build -t ghcr.io/crystal-ameba/ameba .
``` ```
To use the resulting image on a local source folder, mount the current (or target) directory into `/src`: To use the resulting image on a local source folder, mount the current (or target) directory into `/src`:
```sh ```sh
$ docker run -v $(pwd):/src crystal-ameba/ameba $ docker run -v $(pwd):/src ghcr.io/crystal-ameba/ameba
``` ```
Also available on DockerHub: https://hub.docker.com/r/veelenga/ameba Also available on GitHub: https://github.com/crystal-ameba/ameba/pkgs/container/ameba
### From sources ### From sources

View file

@ -10,29 +10,29 @@ module Ameba::AST
describe ".of" do describe ".of" do
context "Crystal::If" do context "Crystal::If" do
it "constructs a branch in If.cond" do it "constructs a branch in If.cond" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method def method
if a = get_something # --> Crystal::Assign if a = get_something # --> Crystal::Assign
puts a puts a
end end
end end
) CRYSTAL
branch.to_s.should eq "a = get_something" branch.to_s.should eq "a = get_something"
end end
it "constructs a branch in If.then" do it "constructs a branch in If.then" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method def method
if true if true
a = 2 # --> Crystal::Assign a = 2 # --> Crystal::Assign
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 2" branch.to_s.should eq "a = 2"
end end
it "constructs a branch in If.else" do it "constructs a branch in If.else" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method def method
if true if true
nil nil
@ -40,45 +40,45 @@ module Ameba::AST
a = 2 # --> Crystal::Assign a = 2 # --> Crystal::Assign
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 2" branch.to_s.should eq "a = 2"
end end
it "constructs a branch in inline If" do it "constructs a branch in inline If" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
a = 0 if a == 2 # --> Crystal::Assign a = 0 if a == 2 # --> Crystal::Assign
end end
) CRYSTAL
branch.to_s.should eq "a = 0" branch.to_s.should eq "a = 0"
end end
end end
context "Crystal::Unless" do context "Crystal::Unless" do
it "constructs a branch in Unless.cond" do it "constructs a branch in Unless.cond" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method def method
unless a = get_something # --> Crystal::Assign unless a = get_something # --> Crystal::Assign
puts a puts a
end end
end end
) CRYSTAL
branch.to_s.should eq "a = get_something" branch.to_s.should eq "a = get_something"
end end
it "constructs a branch in Unless.then" do it "constructs a branch in Unless.then" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method def method
unless true unless true
a = 2 # --> Crystal::Assign a = 2 # --> Crystal::Assign
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 2" branch.to_s.should eq "a = 2"
end end
it "constructs a new branch in Unless.else" do it "constructs a new branch in Unless.else" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method def method
unless true unless true
nil nil
@ -86,188 +86,188 @@ module Ameba::AST
a = 2 # --> Crystal::Assign a = 2 # --> Crystal::Assign
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 2" branch.to_s.should eq "a = 2"
end end
it "constructs a branch in inline Unless" do it "constructs a branch in inline Unless" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
(a = 0; b = 3) unless a == 2 # --> Crystal::Expressions (a = 0; b = 3) unless a == 2 # --> Crystal::Expressions
end end
) CRYSTAL
branch.to_s.should eq "(a = 0\nb = 3)" branch.to_s.should eq "(a = 0\nb = 3)"
end end
end end
context "Crystal::BinaryOp" do context "Crystal::BinaryOp" do
it "constructs a branch in left node" do it "constructs a branch in left node" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
(a = 2) && do_something (a = 2) && do_something
end end
) CRYSTAL
branch.to_s.should eq "(a = 2)" branch.to_s.should eq "(a = 2)"
end end
it "constructs a branch in right node" do it "constructs a branch in right node" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
do_something || (a = 0) do_something || (a = 0)
end end
) CRYSTAL
branch.to_s.should eq "(a = 0)" branch.to_s.should eq "(a = 0)"
end end
end end
context "Crystal::Case" do context "Crystal::Case" do
it "constructs a branch in cond" do it "constructs a branch in cond" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
case (a = 2) case (a = 2)
when true then nil when true then nil
end end
end end
) CRYSTAL
branch.to_s.should eq "(a = 2)" branch.to_s.should eq "(a = 2)"
end end
it "constructs a branch in when" do it "constructs a branch in when" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
case a case a
when a = 3 then nil when a = 3 then nil
end end
end end
) CRYSTAL
branch.to_s.should eq "when a = 3\n nil\n" branch.to_s.should eq "when a = 3\n nil\n"
end end
it "constructs a branch in else" do it "constructs a branch in else" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
case a case a
when true then nil when true then nil
else a = 4 else a = 4
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 4" branch.to_s.should eq "a = 4"
end end
end end
context "Crystal::While" do context "Crystal::While" do
it "constructs a branch in cond" do it "constructs a branch in cond" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
while a = 1 while a = 1
nil nil
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 1" branch.to_s.should eq "a = 1"
end end
it "constructs a branch in body" do it "constructs a branch in body" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
while true while true
b = (a = 1) b = (a = 1)
end end
end end
) CRYSTAL
branch.to_s.should eq "b = (a = 1)" branch.to_s.should eq "b = (a = 1)"
end end
end end
context "Crystal::Until" do context "Crystal::Until" do
it "constructs a branch in cond" do it "constructs a branch in cond" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
until a = 1 until a = 1
nil nil
end end
end end
) CRYSTAL
branch.to_s.should eq "a = 1" branch.to_s.should eq "a = 1"
end end
it "constructs a branch in body" do it "constructs a branch in body" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
until false until false
b = (a = 1) b = (a = 1)
end end
end end
) CRYSTAL
branch.to_s.should eq "b = (a = 1)" branch.to_s.should eq "b = (a = 1)"
end end
end end
context "Crystal::ExceptionHandler" do context "Crystal::ExceptionHandler" do
it "constructs a branch in body" do it "constructs a branch in body" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
a = 1 a = 1
rescue rescue
nil nil
end end
) CRYSTAL
branch.to_s.should eq "a = 1" branch.to_s.should eq "a = 1"
end end
it "constructs a branch in a rescue" do it "constructs a branch in a rescue" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
rescue rescue
a = 1 a = 1
end end
) CRYSTAL
branch.to_s.should eq "a = 1" branch.to_s.should eq "a = 1"
end end
it "constructs a branch in else" do it "constructs a branch in else" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
rescue rescue
else else
a = 1 a = 1
end end
) CRYSTAL
branch.to_s.should eq "a = 1" branch.to_s.should eq "a = 1"
end end
it "constructs a branch in ensure" do it "constructs a branch in ensure" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
rescue rescue
ensure ensure
a = 1 a = 1
end end
) CRYSTAL
branch.to_s.should eq "a = 1" branch.to_s.should eq "a = 1"
end end
end end
context "Crystal::MacroIf" do context "Crystal::MacroIf" do
it "constructs a branch in cond" do it "constructs a branch in cond" do
branch = branch_of_assign_in_def %( branch = branch_of_assign_in_def <<-CRYSTAL
def method(a) def method(a)
{% if a = 2 %} {% if a = 2 %}
{% end %} {% end %}
end end
) CRYSTAL
branch.to_s.should eq "a = 2" branch.to_s.should eq "a = 2"
end end
it "constructs a branch in then" do it "constructs a branch in then" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method(a) def method(a)
{% if true %} {% if true %}
a = 2 a = 2
{% end %} {% end %}
end end
) CRYSTAL
branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first) branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first)
branch.to_s.strip.should eq "a = 2" branch.to_s.strip.should eq "a = 2"
end end
@ -275,24 +275,24 @@ module Ameba::AST
context "Crystal::MacroFor" do context "Crystal::MacroFor" do
it "constructs a branch in body" do it "constructs a branch in body" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method(a) def method(a)
{% for x in [1, 2, 3] %} {% for x in [1, 2, 3] %}
a = 2 a = 2
{% end %} {% end %}
end end
) CRYSTAL
branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first) branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first)
branch.to_s.strip.should eq "a = 2" branch.to_s.strip.should eq "a = 2"
end end
end end
it "returns nil if branch does not exist" do it "returns nil if branch does not exist" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method def method
a = 2 a = 2
end end
) CRYSTAL
branch = Branch.of(nodes.assign_nodes.first, nodes.def_nodes.first) branch = Branch.of(nodes.assign_nodes.first, nodes.def_nodes.first)
branch.should be_nil branch.should be_nil
end end
@ -300,11 +300,11 @@ module Ameba::AST
describe "#initialize" do describe "#initialize" do
it "creates new branch" do it "creates new branch" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
if true if true
a = 2 a = 2
end end
) CRYSTAL
branchable = Branchable.new nodes.if_nodes.first branchable = Branchable.new nodes.if_nodes.first
branch = Branch.new nodes.assign_nodes.first, branchable branch = Branch.new nodes.assign_nodes.first, branchable
branch.node.should_not be_nil branch.node.should_not be_nil
@ -313,22 +313,22 @@ module Ameba::AST
describe "delegation" do describe "delegation" do
it "delegates to_s to node" do it "delegates to_s to node" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
if true if true
a = 2 a = 2
end end
) CRYSTAL
branchable = Branchable.new nodes.if_nodes.first branchable = Branchable.new nodes.if_nodes.first
branch = Branch.new nodes.assign_nodes.first, branchable branch = Branch.new nodes.assign_nodes.first, branchable
branch.to_s.should eq branch.node.to_s branch.to_s.should eq branch.node.to_s
end end
it "delegates locations to node" do it "delegates locations to node" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
if true if true
a = 2 a = 2
end end
) CRYSTAL
branchable = Branchable.new nodes.if_nodes.first branchable = Branchable.new nodes.if_nodes.first
branch = Branch.new nodes.assign_nodes.first, branchable branch = Branch.new nodes.assign_nodes.first, branchable
branch.location.should eq branch.node.location branch.location.should eq branch.node.location
@ -338,22 +338,22 @@ module Ameba::AST
describe "#in_loop?" do describe "#in_loop?" do
it "returns true if branch is in a loop" do it "returns true if branch is in a loop" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
while true while true
a = 1 a = 1
end end
) CRYSTAL
branchable = Branchable.new nodes.while_nodes.first branchable = Branchable.new nodes.while_nodes.first
branch = Branch.new nodes.assign_nodes.first, branchable branch = Branch.new nodes.assign_nodes.first, branchable
branch.in_loop?.should be_true branch.in_loop?.should be_true
end end
it "returns false if branch is not in a loop" do it "returns false if branch is not in a loop" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
if a > 2 if a > 2
a = 1 a = 1
end end
) CRYSTAL
branchable = Branchable.new nodes.if_nodes.first branchable = Branchable.new nodes.if_nodes.first
branch = Branch.new nodes.assign_nodes.first, branchable branch = Branch.new nodes.assign_nodes.first, branchable
branch.in_loop?.should be_false branch.in_loop?.should be_false

View file

@ -4,20 +4,20 @@ module Ameba::AST
describe Branchable do describe Branchable do
describe "#initialize" do describe "#initialize" do
it "creates a new branchable" do it "creates a new branchable" do
branchable = Branchable.new as_node %(a = 2 if true) branchable = Branchable.new as_node "a = 2 if true"
branchable.node.should_not be_nil branchable.node.should_not be_nil
end end
end end
describe "delegation" do describe "delegation" do
it "delegates to_s to @node" do it "delegates to_s to @node" do
node = as_node %(a = 2 if true) node = as_node "a = 2 if true"
branchable = Branchable.new node branchable = Branchable.new node
branchable.to_s.should eq node.to_s branchable.to_s.should eq node.to_s
end end
it "delegates locations to @node" do it "delegates locations to @node" do
node = as_node %(a = 2 if true) node = as_node "a = 2 if true"
branchable = Branchable.new node branchable = Branchable.new node
branchable.location.should eq node.location branchable.location.should eq node.location
branchable.end_location.should eq node.end_location branchable.end_location.should eq node.end_location
@ -26,22 +26,22 @@ module Ameba::AST
describe "#loop?" do describe "#loop?" do
it "returns true if it is a while loop" do it "returns true if it is a while loop" do
branchable = Branchable.new as_node %(while true; a = 2; end) branchable = Branchable.new as_node "while true; a = 2; end"
branchable.loop?.should be_true branchable.loop?.should be_true
end end
it "returns true if it is the until loop" do it "returns true if it is the until loop" do
branchable = Branchable.new as_node %(until false; a = 2; end) branchable = Branchable.new as_node "until false; a = 2; end"
branchable.loop?.should be_true branchable.loop?.should be_true
end end
it "returns true if it is loop" do it "returns true if it is loop" do
branchable = Branchable.new as_node %(loop {}) branchable = Branchable.new as_node "loop {}"
branchable.loop?.should be_true branchable.loop?.should be_true
end end
it "returns false otherwise" do it "returns false otherwise" do
branchable = Branchable.new as_node %(a = 2 if true) branchable = Branchable.new as_node "a = 2 if true"
branchable.loop?.should be_false branchable.loop?.should be_false
end end
end end

View file

@ -18,7 +18,7 @@ module Ameba::AST
end end
it "delegates locations to @node" do it "delegates locations to @node" do
node = as_node %(break if true) node = as_node("break if true")
flow_expression = FlowExpression.new node, false flow_expression = FlowExpression.new node, false
flow_expression.location.should eq node.location flow_expression.location.should eq node.location
flow_expression.end_location.should eq node.end_location flow_expression.end_location.should eq node.end_location
@ -27,20 +27,20 @@ module Ameba::AST
describe "#unreachable_nodes" do describe "#unreachable_nodes" do
it "returns unreachable nodes" do it "returns unreachable nodes" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def foobar def foobar
return return
a = 1 a = 1
a = 2 a = 2
end end
) CRYSTAL
node = nodes.expressions_nodes.first node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.should eq nodes.assign_nodes flow_expression.unreachable_nodes.should eq nodes.assign_nodes
end end
it "returns nil if there is no unreachable node after loop" do it "returns nil if there is no unreachable node after loop" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def run def run
idx = items.size - 1 idx = items.size - 1
while 0 <= idx while 0 <= idx
@ -49,19 +49,19 @@ module Ameba::AST
puts "foo" puts "foo"
end end
) CRYSTAL
node = nodes.expressions_nodes.first node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.empty?.should eq true flow_expression.unreachable_nodes.empty?.should eq true
end end
it "returns nil if there is no unreachable node" do it "returns nil if there is no unreachable node" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def foobar def foobar
a = 1 a = 1
return a return a
end end
) CRYSTAL
node = nodes.expressions_nodes.first node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.empty?.should eq true flow_expression.unreachable_nodes.empty?.should eq true

View file

@ -49,14 +49,14 @@ module Ameba::AST
describe "#references?" do describe "#references?" do
it "returns true if current scope references variable" do it "returns true if current scope references variable" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method def method
a = 2 a = 2
block do block do
3.times { |i| a = a + i } 3.times { |i| a = a + i }
end end
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node scope.add_variable var_node
@ -69,14 +69,14 @@ module Ameba::AST
end end
it "returns false if inner scopes are not checked" do it "returns false if inner scopes are not checked" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method def method
a = 2 a = 2
block do block do
3.times { |i| a = a + i } 3.times { |i| a = a + i }
end end
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node scope.add_variable var_node
@ -89,7 +89,7 @@ module Ameba::AST
end end
it "returns false if current scope does not reference variable" do it "returns false if current scope does not reference variable" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method def method
a = 2 a = 2
block do block do
@ -97,7 +97,7 @@ module Ameba::AST
3.times { |i| b = b + i } 3.times { |i| b = b + i }
end end
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node scope.add_variable var_node
@ -150,57 +150,53 @@ module Ameba::AST
describe "#block?" do describe "#block?" do
it "returns true if Crystal::Block" do it "returns true if Crystal::Block" do
nodes = as_nodes %( nodes = as_nodes("3.times {}")
3.times {}
)
scope = Scope.new nodes.block_nodes.first scope = Scope.new nodes.block_nodes.first
scope.block?.should be_true scope.block?.should be_true
end end
it "returns false otherwise" do it "returns false otherwise" do
scope = Scope.new as_node "a = 1" scope = Scope.new as_node("a = 1")
scope.block?.should be_false scope.block?.should be_false
end end
end end
describe "#spawn_block?" do describe "#spawn_block?" do
it "returns true if a node is a spawn block" do it "returns true if a node is a spawn block" do
nodes = as_nodes %( nodes = as_nodes("spawn {}")
spawn {}
)
scope = Scope.new nodes.block_nodes.first scope = Scope.new nodes.block_nodes.first
scope.spawn_block?.should be_true scope.spawn_block?.should be_true
end end
it "returns false otherwise" do it "returns false otherwise" do
scope = Scope.new as_node "a = 1" scope = Scope.new as_node("a = 1")
scope.spawn_block?.should be_false scope.spawn_block?.should be_false
end end
end end
describe "#in_macro?" do describe "#in_macro?" do
it "returns true if Crystal::Macro" do it "returns true if Crystal::Macro" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
macro included macro included
end end
) CRYSTAL
scope = Scope.new nodes.macro_nodes.first scope = Scope.new nodes.macro_nodes.first
scope.in_macro?.should be_true scope.in_macro?.should be_true
end end
it "returns true if node is nested to Crystal::Macro" do it "returns true if node is nested to Crystal::Macro" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
macro included macro included
{{ @type.each do |type| a = type end }} {{ @type.each do |type| a = type end }}
end end
) CRYSTAL
outer_scope = Scope.new nodes.macro_nodes.first outer_scope = Scope.new nodes.macro_nodes.first
scope = Scope.new nodes.block_nodes.first, outer_scope scope = Scope.new nodes.block_nodes.first, outer_scope
scope.in_macro?.should be_true scope.in_macro?.should be_true
end end
it "returns false otherwise" do it "returns false otherwise" do
scope = Scope.new as_node "a = 1" scope = Scope.new as_node("a = 1")
scope.in_macro?.should be_false scope.in_macro?.should be_false
end end
end end

View file

@ -41,15 +41,14 @@ module Ameba::AST
describe "#branch" do describe "#branch" do
it "returns the branch of the assignment" do it "returns the branch of the assignment" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method(a) def method(a)
if a if a
a = 3 # --> Crystal::Expressions a = 3 # --> Crystal::Expressions
puts a puts a
end end
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
variable = Variable.new(nodes.var_nodes.first, scope) variable = Variable.new(nodes.var_nodes.first, scope)
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope) assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
@ -58,7 +57,7 @@ module Ameba::AST
end end
it "returns inner branch" do it "returns inner branch" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method(a, b) def method(a, b)
if a if a
if b if b
@ -66,7 +65,7 @@ module Ameba::AST
end end
end end
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
variable = Variable.new(nodes.var_nodes.first, scope) variable = Variable.new(nodes.var_nodes.first, scope)
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope) assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
@ -75,12 +74,11 @@ module Ameba::AST
end end
it "returns nil if assignment does not have a branch" do it "returns nil if assignment does not have a branch" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method(a) def method(a)
a = 2 a = 2
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
variable = Variable.new(nodes.var_nodes.first, scope) variable = Variable.new(nodes.var_nodes.first, scope)
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope) assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
@ -90,12 +88,11 @@ module Ameba::AST
describe "#transformed?" do describe "#transformed?" do
it "returns false if the assignment is not transformed by the compiler" do it "returns false if the assignment is not transformed by the compiler" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method(a) def method(a)
a = 2 a = 2
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
variable = Variable.new(nodes.var_nodes.first, scope) variable = Variable.new(nodes.var_nodes.first, scope)
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope) assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
@ -103,11 +100,10 @@ module Ameba::AST
end end
it "returns true if the assignment is transformed by the compiler" do it "returns true if the assignment is transformed by the compiler" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
array.each do |(a, b)| array.each do |(a, b)|
end end
) CRYSTAL
scope = Scope.new nodes.block_nodes.first scope = Scope.new nodes.block_nodes.first
variable = Variable.new(nodes.var_nodes.first, scope) variable = Variable.new(nodes.var_nodes.first, scope)
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope) assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)

View file

@ -79,12 +79,12 @@ module Ameba::AST
describe "#captured_by_block?" do describe "#captured_by_block?" do
it "returns truthy if the variable is captured by block" do it "returns truthy if the variable is captured by block" do
nodes = as_nodes %( nodes = as_nodes <<-CRYSTAL
def method def method
a = 2 a = 2
3.times { |i| a = a + i } 3.times { |i| a = a + i }
end end
) CRYSTAL
scope = Scope.new nodes.def_nodes.first scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node scope.add_variable var_node
@ -95,12 +95,12 @@ module Ameba::AST
variable.captured_by_block?.should be_truthy variable.captured_by_block?.should be_truthy
end end
it "returns falsey if the variable is not captured by the block" do it "returns falsy if the variable is not captured by the block" do
scope = Scope.new as_node %( scope = Scope.new as_node <<-CRYSTAL
def method def method
a = 1 a = 1
end end
) CRYSTAL
scope.add_variable Crystal::Var.new "a" scope.add_variable Crystal::Var.new "a"
variable = scope.variables.first variable = scope.variables.first
variable.captured_by_block?.should be_falsey variable.captured_by_block?.should be_falsey

View file

@ -19,33 +19,33 @@ module Ameba::AST
end end
it "is 1 if there is Macro::For" do it "is 1 if there is Macro::For" do
code = %( code = <<-CRYSTAL
def initialize() def initialize
{% for c in ALL_NODES %} {% for c in ALL_NODES %}
true || false true || false
{% end %} {% end %}
end end
) CRYSTAL
node = Crystal::Parser.new(code).parse node = Crystal::Parser.new(code).parse
visitor = CountingVisitor.new node visitor = CountingVisitor.new node
visitor.count.should eq 1 visitor.count.should eq 1
end end
it "is 1 if there is Macro::If" do it "is 1 if there is Macro::If" do
code = %( code = <<-CRYSTAL
def initialize() def initialize
{% if foo.bar? %} {% if foo.bar? %}
true || false true || false
{% end %} {% end %}
end end
) CRYSTAL
node = Crystal::Parser.new(code).parse node = Crystal::Parser.new(code).parse
visitor = CountingVisitor.new node visitor = CountingVisitor.new node
visitor.count.should eq 1 visitor.count.should eq 1
end end
it "increases count for every exhaustive case" do it "increases count for every exhaustive case" do
code = %( code = <<-CRYSTAL
def hello(a : Int32 | Int64 | Float32 | Float64) def hello(a : Int32 | Int64 | Float32 | Float64)
case a case a
in Int32 then "int32" in Int32 then "int32"
@ -54,7 +54,7 @@ module Ameba::AST
in Float64 then "float64" in Float64 then "float64"
end end
end end
) CRYSTAL
node = Crystal::Parser.new(code).parse node = Crystal::Parser.new(code).parse
visitor = CountingVisitor.new node visitor = CountingVisitor.new node
visitor.count.should eq 2 visitor.count.should eq 2

View file

@ -6,17 +6,17 @@ module Ameba::AST
describe FlowExpressionVisitor do describe FlowExpressionVisitor do
it "creates an expression for return" do it "creates an expression for return" do
rule = FlowExpressionRule.new rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new %( FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
def foo def foo
return :bar return :bar
end end
) CRYSTAL
rule.expressions.size.should eq 1 rule.expressions.size.should eq 1
end end
it "can create multiple expressions" do it "can create multiple expressions" do
rule = FlowExpressionRule.new rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new %( FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
def foo def foo
if bar if bar
return :baz return :baz
@ -24,42 +24,42 @@ module Ameba::AST
return :foobar return :foobar
end end
end end
) CRYSTAL
rule.expressions.size.should eq 3 rule.expressions.size.should eq 3
end end
it "properly creates nested flow expressions" do it "properly creates nested flow expressions" do
rule = FlowExpressionRule.new rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new %( FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
def foo def foo
return( return (
loop do loop do
break if a > 1 break if a > 1
return a return a
end end
) )
end end
) CRYSTAL
rule.expressions.size.should eq 4 rule.expressions.size.should eq 4
end end
it "creates an expression for break" do it "creates an expression for break" do
rule = FlowExpressionRule.new rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new %( FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
while true while true
break break
end end
) CRYSTAL
rule.expressions.size.should eq 1 rule.expressions.size.should eq 1
end end
it "creates an expression for next" do it "creates an expression for next" do
rule = FlowExpressionRule.new rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new %( FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
while true while true
next if something next if something
end end
) CRYSTAL
rule.expressions.size.should eq 1 rule.expressions.size.should eq 1
end end
end end

View file

@ -5,11 +5,11 @@ module Ameba::AST
rule = RedundantControlExpressionRule.new rule = RedundantControlExpressionRule.new
describe RedundantControlExpressionVisitor do describe RedundantControlExpressionVisitor do
node = as_node %( node = as_node <<-CRYSTAL
a = 1 a = 1
b = 2 b = 2
return a + b return a + b
) CRYSTAL
subject = RedundantControlExpressionVisitor.new(rule, source, node) subject = RedundantControlExpressionVisitor.new(rule, source, node)
it "assigns valid attributes" do it "assigns valid attributes" do

View file

@ -4,37 +4,37 @@ module Ameba::AST
describe ScopeVisitor do describe ScopeVisitor do
it "creates a scope for the def" do it "creates a scope for the def" do
rule = ScopeRule.new rule = ScopeRule.new
ScopeVisitor.new rule, Source.new %( ScopeVisitor.new rule, Source.new <<-CRYSTAL
def method def method
end end
) CRYSTAL
rule.scopes.size.should eq 1 rule.scopes.size.should eq 1
end end
it "creates a scope for the proc" do it "creates a scope for the proc" do
rule = ScopeRule.new rule = ScopeRule.new
ScopeVisitor.new rule, Source.new %( ScopeVisitor.new rule, Source.new <<-CRYSTAL
-> {} -> {}
) CRYSTAL
rule.scopes.size.should eq 1 rule.scopes.size.should eq 1
end end
it "creates a scope for the block" do it "creates a scope for the block" do
rule = ScopeRule.new rule = ScopeRule.new
ScopeVisitor.new rule, Source.new %( ScopeVisitor.new rule, Source.new <<-CRYSTAL
3.times {} 3.times {}
) CRYSTAL
rule.scopes.size.should eq 2 rule.scopes.size.should eq 2
end end
context "inner scopes" do context "inner scopes" do
it "creates scope for block inside def" do it "creates scope for block inside def" do
rule = ScopeRule.new rule = ScopeRule.new
ScopeVisitor.new rule, Source.new %( ScopeVisitor.new rule, Source.new <<-CRYSTAL
def method def method
3.times {} 3.times {}
end end
) CRYSTAL
rule.scopes.size.should eq 2 rule.scopes.size.should eq 2
rule.scopes.last.outer_scope.should_not be_nil rule.scopes.last.outer_scope.should_not be_nil
rule.scopes.first.outer_scope.should eq rule.scopes.last rule.scopes.first.outer_scope.should eq rule.scopes.last
@ -42,11 +42,11 @@ module Ameba::AST
it "creates scope for block inside block" do it "creates scope for block inside block" do
rule = ScopeRule.new rule = ScopeRule.new
ScopeVisitor.new rule, Source.new %( ScopeVisitor.new rule, Source.new <<-CRYSTAL
3.times do 3.times do
2.times {} 2.times {}
end end
) CRYSTAL
rule.scopes.size.should eq 3 rule.scopes.size.should eq 3
inner_block = rule.scopes.first inner_block = rule.scopes.first
outer_block = rule.scopes.last outer_block = rule.scopes.last

View file

@ -4,10 +4,12 @@ module Ameba::AST
describe TopLevelNodesVisitor do describe TopLevelNodesVisitor do
describe "#require_nodes" do describe "#require_nodes" do
it "returns require node" do it "returns require node" do
source = Source.new %( source = Source.new <<-CRYSTAL
require "foo" require "foo"
def bar; end
) def bar
end
CRYSTAL
visitor = TopLevelNodesVisitor.new(source.ast) visitor = TopLevelNodesVisitor.new(source.ast)
visitor.require_nodes.size.should eq 1 visitor.require_nodes.size.should eq 1
visitor.require_nodes.first.to_s.should eq %q(require "foo") visitor.require_nodes.first.to_s.should eq %q(require "foo")

View file

@ -26,135 +26,135 @@ module Ameba
end end
it "disables a rule with a comment directive" do it "disables a rule with a comment directive" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
# ameba:disable #{NamedRule.name} # ameba:disable #{NamedRule.name}
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {1, 12}, message: "Error!") source.add_issue(NamedRule.new, location: {1, 12}, message: "Error!")
s.should be_valid source.should be_valid
end end
it "disables a rule with a line that ends with a comment directive" do it "disables a rule with a line that ends with a comment directive" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
Time.epoch(1483859302) # ameba:disable #{NamedRule.name} Time.epoch(1483859302) # ameba:disable #{NamedRule.name}
) CRYSTAL
s.add_issue(NamedRule.new, location: {1, 12}, message: "Error!") source.add_issue(NamedRule.new, location: {1, 12}, message: "Error!")
s.should be_valid source.should be_valid
end end
it "does not disable a rule of a different name" do it "does not disable a rule of a different name" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
# ameba:disable WrongName # ameba:disable WrongName
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {2, 12}, message: "Error!") source.add_issue(NamedRule.new, location: {2, 12}, message: "Error!")
s.should_not be_valid source.should_not be_valid
end end
it "disables a rule if multiple rule names provided" do it "disables a rule if multiple rule names provided" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
# ameba:disable SomeRule LargeNumbers #{NamedRule.name} SomeOtherRule # ameba:disable SomeRule LargeNumbers #{NamedRule.name} SomeOtherRule
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {2, 12}, message: "") source.add_issue(NamedRule.new, location: {2, 12}, message: "")
s.should be_valid source.should be_valid
end end
it "disables a rule if multiple rule names are separated by comma" do it "disables a rule if multiple rule names are separated by comma" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
# ameba:disable SomeRule, LargeNumbers, #{NamedRule.name}, SomeOtherRule # ameba:disable SomeRule, LargeNumbers, #{NamedRule.name}, SomeOtherRule
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {2, 12}, message: "") source.add_issue(NamedRule.new, location: {2, 12}, message: "")
s.should be_valid source.should be_valid
end end
it "does not disable if multiple rule names used without required one" do it "does not disable if multiple rule names used without required one" do
s = Source.new %( source = Source.new <<-CRYSTAL
# ameba:disable SomeRule, SomeOtherRule LargeNumbers # ameba:disable SomeRule, SomeOtherRule LargeNumbers
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {2, 12}, message: "") source.add_issue(NamedRule.new, location: {2, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
it "does not disable if comment directive has wrong place" do it "does not disable if comment directive has wrong place" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
# ameba:disable #{NamedRule.name} # ameba:disable #{NamedRule.name}
# #
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {3, 12}, message: "") source.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
it "does not disable if comment directive added to the wrong line" do it "does not disable if comment directive added to the wrong line" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
if use_epoch? # ameba:disable #{NamedRule.name} if use_epoch? # ameba:disable #{NamedRule.name}
Time.epoch(1483859302) Time.epoch(1483859302)
end end
) CRYSTAL
s.add_issue(NamedRule.new, location: {3, 12}, message: "") source.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
it "does not disable if that is not a comment directive" do it "does not disable if that is not a comment directive" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
"ameba:disable #{NamedRule.name}" "ameba:disable #{NamedRule.name}"
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {3, 12}, message: "") source.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
it "does not disable if that is a commented out directive" do it "does not disable if that is a commented out directive" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
# # ameba:disable #{NamedRule.name} # # ameba:disable #{NamedRule.name}
Time.epoch(1483859302) Time.epoch(1483859302)
) CRYSTAL
s.add_issue(NamedRule.new, location: {3, 12}, message: "") source.add_issue(NamedRule.new, location: {3, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
it "does not disable if that is an inline commented out directive" do it "does not disable if that is an inline commented out directive" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
a = 1 # Disable it: # ameba:disable #{NamedRule.name} a = 1 # Disable it: # ameba:disable #{NamedRule.name}
) CRYSTAL
s.add_issue(NamedRule.new, location: {2, 12}, message: "") source.add_issue(NamedRule.new, location: {2, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
context "with group name" do context "with group name" do
it "disables one rule with a group" do it "disables one rule with a group" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
a = 1 # ameba:disable #{DummyRule.rule_name} a = 1 # ameba:disable #{DummyRule.rule_name}
) CRYSTAL
s.add_issue(DummyRule.new, location: {1, 12}, message: "") source.add_issue(DummyRule.new, location: {1, 12}, message: "")
s.should be_valid source.should be_valid
end end
it "doesn't disable others rules" do it "doesn't disable others rules" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
a = 1 # ameba:disable #{DummyRule.rule_name} a = 1 # ameba:disable #{DummyRule.rule_name}
) CRYSTAL
s.add_issue(NamedRule.new, location: {2, 12}, message: "") source.add_issue(NamedRule.new, location: {2, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
it "disables a hole group of rules" do it "disables a hole group of rules" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
a = 1 # ameba:disable #{DummyRule.group_name} a = 1 # ameba:disable #{DummyRule.group_name}
) CRYSTAL
s.add_issue(DummyRule.new, location: {1, 12}, message: "") source.add_issue(DummyRule.new, location: {1, 12}, message: "")
s.should be_valid source.should be_valid
end end
it "does not disable rules which do not belong to the group" do it "does not disable rules which do not belong to the group" do
s = Source.new %Q( source = Source.new <<-CRYSTAL
a = 1 # ameba:disable Lint a = 1 # ameba:disable Lint
) CRYSTAL
s.add_issue(DummyRule.new, location: {2, 12}, message: "") source.add_issue(DummyRule.new, location: {2, 12}, message: "")
s.should_not be_valid source.should_not be_valid
end end
end end
end end

View file

@ -34,7 +34,7 @@ module Ameba::Rule::Layout
end end
context "properties" do context "properties" do
it "allows to configure max length of the line" do it "#max_length" do
rule = LineLength.new rule = LineLength.new
rule.max_length = long_line.size rule.max_length = long_line.size

View file

@ -5,7 +5,7 @@ module Ameba::Rule::Layout
describe TrailingWhitespace do describe TrailingWhitespace do
it "passes if all lines do not have trailing whitespace" do it "passes if all lines do not have trailing whitespace" do
expect_no_issues subject, "no-whispace" expect_no_issues subject, "no-whitespace"
end end
it "fails if there is a line with trailing whitespace" do it "fails if there is a line with trailing whitespace" do
@ -15,16 +15,5 @@ module Ameba::Rule::Layout
expect_correction source, "whitespace at the end" expect_correction source, "whitespace at the end"
end end
it "reports rule, pos and message" do
source = Source.new "a = 1\n b = 2 ", "source.cr"
subject.catch(source).should_not be_valid
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:7"
issue.end_location.to_s.should eq "source.cr:2:7"
issue.message.should eq "Trailing whitespace detected"
end
end end
end end

View file

@ -94,16 +94,6 @@ module Ameba::Rule::Lint
a a
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
source = Source.new "a != true", "source.cr"
subject.catch(source)
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.message.should eq "Comparison to a boolean is pointless"
end
end end
context "boolean on the left" do context "boolean on the left" do
@ -165,17 +155,6 @@ module Ameba::Rule::Lint
a a
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
source = Source.new "true != a", "source.cr"
subject.catch(source).should_not be_valid
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:9"
issue.message.should eq "Comparison to a boolean is pointless"
end
end end
end end
end end

View file

@ -28,16 +28,5 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new "pp! :foo", "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:8"
issue.message.should eq "Possibly forgotten debug-related `pp!` call detected"
end
end end
end end

View file

@ -31,16 +31,5 @@ module Ameba::Rule::Lint
expect_no_corrections source expect_no_corrections source
end end
it "reports rule, pos and message" do
s = Source.new "debugger", "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:8"
issue.message.should eq "Possible forgotten debugger statement detected"
end
end end
end end

View file

@ -17,33 +17,10 @@ module Ameba::Rule::Lint
require "big" require "big"
require "math" require "math"
require "big" require "big"
# ^{} error: Duplicated require of `big` # ^^^^^^^^^^^ error: Duplicated require of `big`
CRYSTAL CRYSTAL
expect_no_corrections source expect_no_corrections source
end end
it "reports rule, pos and message" do
source = Source.new %(
require "./thing"
require "./thing"
require "./another_thing"
require "./another_thing"
), "source.cr"
subject.catch(source).should_not be_valid
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:1"
issue.end_location.to_s.should eq ""
issue.message.should eq "Duplicated require of `./thing`"
issue = source.issues.last
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:4:1"
issue.end_location.to_s.should eq ""
issue.message.should eq "Duplicated require of `./another_thing`"
end
end end
end end

View file

@ -3,17 +3,17 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Lint::EmptyExpression.new subject = Rule::Lint::EmptyExpression.new
def it_detects_empty_expression(code) private def it_detects_empty_expression(code, *, file = __FILE__, line = __LINE__)
it "detects empty expression" do it %(detects empty expression "#{code}"), file, line do
s = Source.new code s = Source.new code
rule = Rule::Lint::EmptyExpression.new rule = Rule::Lint::EmptyExpression.new
rule.catch(s).should_not be_valid rule.catch(s).should_not be_valid, file: file, line: line
end end
end end
describe Rule::Lint::EmptyExpression do describe Rule::Lint::EmptyExpression do
it "passes if there is no empty expression" do it "passes if there is no empty expression" do
s = Source.new %( s = Source.new <<-CRYSTAL
def method() def method()
end end
@ -30,7 +30,7 @@ module Ameba
begin "" end begin "" end
[nil] << nil [nil] << nil
) CRYSTAL
subject.catch(s).should be_valid subject.catch(s).should be_valid
end end
@ -84,11 +84,6 @@ module Ameba
it_detects_empty_expression %( it_detects_empty_expression %(
begin; end begin; end
) )
it_detects_empty_expression %(
begin
nil
end
)
it_detects_empty_expression %( it_detects_empty_expression %(
begin begin
() ()
@ -110,18 +105,5 @@ module Ameba
) )
subject.catch(s).should be_valid subject.catch(s).should be_valid
end end
it "reports rule, location and message" do
s = Source.new %(
if ()
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:4"
issue.end_location.to_s.should eq "source.cr:1:5"
issue.message.should eq "Avoid empty expressions"
end
end end
end end

View file

@ -64,21 +64,5 @@ module Ameba::Rule::Lint
end end
CRYSTAL CRYSTAL
end end
it "reports rule, message and location" do
s = Source.new %(
a = 1
loop do
# comment goes here
end
), "source.cr"
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:1"
issue.end_location.to_s.should eq "source.cr:4:3"
issue.message.should eq EmptyLoop::MSG
end
end end
end end

View file

@ -32,17 +32,5 @@ module Ameba::Rule::Lint
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicated keys in hash literal: "key1", "key2" # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicated keys in hash literal: "key1", "key2"
CRYSTAL CRYSTAL
end end
it "reports rule, location and message" do
s = Source.new %q(
h = {"a" => 1, "a" => 2}
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:5"
issue.end_location.to_s.should eq "source.cr:1:24"
issue.message.should eq %(Duplicated keys in hash literal: "a")
end
end end
end end

View file

@ -58,18 +58,5 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
"foo" == "foo"
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:14"
issue.message.should eq "Comparison always evaluates to true"
end
end end
end end

View file

@ -58,18 +58,5 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
(1..3).index(1).not_nil!
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:8"
issue.end_location.to_s.should eq "source.cr:1:24"
issue.message.should eq "Use `index! {...}` instead of `index {...}.not_nil!`"
end
end end
end end

View file

@ -32,18 +32,5 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
(1..3).first?.not_nil!
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:15"
issue.end_location.to_s.should eq "source.cr:1:22"
issue.message.should eq "Avoid using `not_nil!`"
end
end end
end end

View file

@ -68,14 +68,14 @@ module Ameba::Rule::Lint
end end
context "properties" do context "properties" do
it "allows to configure string_array_unwanted_symbols" do it "#string_array_unwanted_symbols" do
rule = PercentArrays.new rule = PercentArrays.new
rule.string_array_unwanted_symbols = "," rule.string_array_unwanted_symbols = ","
s = Source.new %( %w("one") ) s = Source.new %( %w("one") )
rule.catch(s).should be_valid rule.catch(s).should be_valid
end end
it "allows to configure symbol_array_unwanted_symbols" do it "#symbol_array_unwanted_symbols" do
rule = PercentArrays.new rule = PercentArrays.new
rule.symbol_array_unwanted_symbols = "," rule.symbol_array_unwanted_symbols = ","
s = Source.new %( %i(:one) ) s = Source.new %( %i(:one) )

View file

@ -25,16 +25,5 @@ module Ameba::Rule::Lint
# ^^^^^ error: rand(1) always returns 0 # ^^^^^ error: rand(1) always returns 0
CRYSTAL CRYSTAL
end end
it "reports rule, location and a message" do
s = Source.new "rand(1)", "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:7"
issue.message.should eq "rand(1) always returns 0"
end
end end
end end

View file

@ -77,7 +77,7 @@ module Ameba::Rule::Lint
issue = s.issues.first issue = s.issues.first
issue.rule.should_not be_nil issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:19" issue.location.to_s.should eq "source.cr:2:19"
issue.end_location.to_s.should eq "source.cr:2:29" issue.end_location.to_s.should eq "source.cr:2:28"
issue.message.should eq "Remove redundant with_index" issue.message.should eq "Remove redundant with_index"
end end
end end
@ -155,7 +155,7 @@ module Ameba::Rule::Lint
issue = s.issues.first issue = s.issues.first
issue.rule.should_not be_nil issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:14" issue.location.to_s.should eq "source.cr:2:14"
issue.end_location.to_s.should eq "source.cr:2:29" issue.end_location.to_s.should eq "source.cr:2:28"
issue.message.should eq "Use each instead of each_with_index" issue.message.should eq "Use each instead of each_with_index"
end end
end end

View file

@ -76,7 +76,7 @@ module Ameba::Rule::Lint
issue = s.issues.first issue = s.issues.first
issue.rule.should_not be_nil issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:14" issue.location.to_s.should eq "source.cr:2:14"
issue.end_location.to_s.should eq "source.cr:2:30" issue.end_location.to_s.should eq "source.cr:2:29"
issue.message.should eq "Use `each` instead of `each_with_object`" issue.message.should eq "Use `each` instead of `each_with_object`"
end end
end end

View file

@ -157,20 +157,6 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end end
it "reports rule, location and message" do
source = Source.new %(
foo = 1
3.times { |foo| foo + 1 }
), "source.cr"
subject.catch(source).should_not be_valid
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:12"
issue.end_location.should be_nil
issue.message.should eq "Shadowing outer local variable `foo`"
end
context "macro" do context "macro" do
it "does not report shadowed vars in outer scope" do it "does not report shadowed vars in outer scope" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL

View file

@ -194,24 +194,5 @@ module Ameba::Rule::Lint
end end
CRYSTAL CRYSTAL
end end
it "reports rule, location and message" do
s = Source.new %(
i = 0
while true
i += 1
spawn { i }
end
), "source.cr"
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:4:11"
issue.end_location.to_s.should eq "source.cr:4:11"
issue.message.should eq "Shared variable `i` is used in fiber"
end
end end
end end

View file

@ -23,16 +23,6 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end end
it "reports rule, location and message" do
s = Source.new "def hello end", "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:11"
issue.message.should match /unexpected token: "?end"? \(expected ["'];["'] or newline\)/
end
it "has highest severity" do it "has highest severity" do
subject.severity.should eq Severity::Error subject.severity.should eq Severity::Error
end end

View file

@ -6,7 +6,7 @@ module Ameba::Rule::Lint
describe UnusedArgument do describe UnusedArgument do
it "doesn't report if arguments are used" do it "doesn't report if arguments are used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(a, b, c) def method(a, b, c)
a + b + c a + b + c
end end
@ -16,155 +16,158 @@ module Ameba::Rule::Lint
end end
->(i : Int32) { i + 1 } ->(i : Int32) { i + 1 }
) CRYSTAL
subject.catch(s).should be_valid
end end
it "reports if method argument is unused" do it "reports if method argument is unused" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
def method(a, b, c) def method(a, b, c)
# ^ error: Unused argument `c`. If it's necessary, use `_c` as an argument name to indicate that it won't be used.
a + b a + b
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `c`. If it's necessary, use `_c` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." def method(a, b, _c)
a + b
end
CRYSTAL
end end
it "reports if block argument is unused" do it "reports if block argument is unused" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
[1,2].each_with_index do |a, i| [1, 2].each_with_index do |a, i|
# ^ error: Unused argument `i`. [...]
a a
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `i`. If it's necessary, use `_` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." [1, 2].each_with_index do |a, _|
a
end
CRYSTAL
end end
it "reports if proc argument is unused" do it "reports if proc argument is unused" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
-> (a : Int32, b : String) do -> (a : Int32, b : String) do
# ^ error: Unused argument `b`. If it's necessary, use `_b` as an argument name to indicate that it won't be used.
a = a + 1 a = a + 1
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." -> (a : Int32, _b : String) do
a = a + 1
end
CRYSTAL
end end
it "reports multiple unused args" do it "reports multiple unused args" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
def method(a, b, c) def method(a, b, c)
# ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
# ^ error: Unused argument `b`. If it's necessary, use `_b` as an argument name to indicate that it won't be used.
# ^ error: Unused argument `c`. If it's necessary, use `_c` as an argument name to indicate that it won't be used.
nil nil
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues[0].message.should eq "Unused argument `a`. If it's necessary, use `_a` " \ expect_correction source, <<-CRYSTAL
"as an argument name to indicate that it won't be used." def method(_a, _b, _c)
s.issues[1].message.should eq "Unused argument `b`. If it's necessary, use `_b` " \ nil
"as an argument name to indicate that it won't be used." end
s.issues[2].message.should eq "Unused argument `c`. If it's necessary, use `_c` " \ CRYSTAL
"as an argument name to indicate that it won't be used."
end end
it "doesn't report if it is an instance var argument" do it "doesn't report if it is an instance var argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class A class A
def method(@name) def method(@name)
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if a typed argument is used" do it "doesn't report if a typed argument is used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(x : Int32) def method(x : Int32)
3.times do 3.times do
puts x puts x
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if an argument with default value is used" do it "doesn't report if an argument with default value is used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(x = 1) def method(x = 1)
puts x puts x
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if argument starts with a _" do it "doesn't report if argument starts with a _" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(_x) def method(_x)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it is a block and used" do it "doesn't report if it is a block and used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&block) def method(&block)
block.call block.call
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if block arg is not used" do it "doesn't report if block arg is not used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&block) def method(&block)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if unused and there is yield" do it "doesn't report if unused and there is yield" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&block) def method(&block)
yield 1 yield 1
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it's an anonymous block" do it "doesn't report if it's an anonymous block" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(&) def method(&)
yield 1 yield 1
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if variable is referenced implicitly" do it "doesn't report if variable is referenced implicitly" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class Bar < Foo class Bar < Foo
def method(a, b) def method(a, b)
super super
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if arg if referenced in case" do it "doesn't report if arg if referenced in case" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def foo(a) def foo(a)
case a case a
when /foo/ when /foo/
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if enum in a record" do it "doesn't report if enum in a record" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class Class class Class
record Record do record Record do
enum Enum enum Enum
@ -172,59 +175,49 @@ module Ameba::Rule::Lint
end end
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
context "super" do context "super" do
it "reports if variable is not referenced implicitly by super" do it "reports if variable is not referenced implicitly by super" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
class Bar < Foo class Bar < Foo
def method(a, b) def method(a, b)
# ^ error: Unused argument `b`. If it's necessary, use `_b` as an argument name to indicate that it won't be used.
super a super a
end end
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused argument `b`. If it's necessary, use `_b` " \
"as an argument name to indicate that it won't be used."
end
it "reports rule, location and message" do expect_correction source, <<-CRYSTAL
s = Source.new %( class Bar < Foo
def method(a) def method(a, _b)
super a
end end
), "source.cr" end
subject.catch(s).should_not be_valid CRYSTAL
issue = s.issues.first
issue.rule.should_not be_nil
issue.message.should eq "Unused argument `a`. If it's necessary, use `_a` " \
"as an argument name to indicate that it won't be used."
issue.location.to_s.should eq "source.cr:1:12"
end end
end end
context "macro" do context "macro" do
it "doesn't report if it is a used macro argument" do it "doesn't report if it is a used macro argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
macro my_macro(arg) macro my_macro(arg)
{% arg %} {% arg %}
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it is a used macro block argument" do it "doesn't report if it is a used macro block argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
macro my_macro(&block) macro my_macro(&block)
{% block %} {% block %}
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report used macro args with equal names in record" do it "doesn't report used macro args with equal names in record" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
record X do record X do
macro foo(a, b) macro foo(a, b)
{{ a }} + {{ b }} {{ a }} + {{ b }}
@ -234,12 +227,11 @@ module Ameba::Rule::Lint
{{ a }} + {{ b }} + {{ c }} {{ a }} + {{ b }} + {{ c }}
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report used args in macro literals" do it "doesn't report used args in macro literals" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def print(f : Array(U)) forall U def print(f : Array(U)) forall U
f.size.times do |i| f.size.times do |i|
{% if U == Float64 %} {% if U == Float64 %}
@ -249,65 +241,73 @@ module Ameba::Rule::Lint
{% end %} {% end %}
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
end end
context "properties" do context "properties" do
describe "#ignore_defs" do describe "#ignore_defs" do
it "lets the rule to ignore def scopes if true" do it "lets the rule to ignore def scopes if true" do
subject.ignore_defs = true rule = UnusedArgument.new
s = Source.new %( rule.ignore_defs = true
expect_no_issues rule, <<-CRYSTAL
def method(a) def method(a)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "lets the rule not to ignore def scopes if false" do it "lets the rule not to ignore def scopes if false" do
subject.ignore_defs = false rule = UnusedArgument.new
s = Source.new %( rule.ignore_defs = false
expect_issue rule, <<-CRYSTAL
def method(a) def method(a)
# ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
end end
) CRYSTAL
subject.catch(s).should_not be_valid
end end
end end
context "#ignore_blocks" do context "#ignore_blocks" do
it "lets the rule to ignore block scopes if true" do it "lets the rule to ignore block scopes if true" do
subject.ignore_blocks = true rule = UnusedArgument.new
s = Source.new %( rule.ignore_blocks = true
expect_no_issues rule, <<-CRYSTAL
3.times { |i| puts "yo!" } 3.times { |i| puts "yo!" }
) CRYSTAL
subject.catch(s).should be_valid
end end
it "lets the rule not to ignore block scopes if false" do it "lets the rule not to ignore block scopes if false" do
subject.ignore_blocks = false rule = UnusedArgument.new
s = Source.new %( rule.ignore_blocks = false
expect_issue rule, <<-CRYSTAL
3.times { |i| puts "yo!" } 3.times { |i| puts "yo!" }
) # ^ error: Unused argument `i`. If it's necessary, use `_` as an argument name to indicate that it won't be used.
subject.catch(s).should_not be_valid CRYSTAL
end end
end end
context "#ignore_procs" do context "#ignore_procs" do
it "lets the rule to ignore proc scopes if true" do it "lets the rule to ignore proc scopes if true" do
subject.ignore_procs = true rule = UnusedArgument.new
s = Source.new %( rule.ignore_procs = true
expect_no_issues rule, <<-CRYSTAL
->(a : Int32) {} ->(a : Int32) {}
) CRYSTAL
subject.catch(s).should be_valid
end end
it "lets the rule not to ignore proc scopes if false" do it "lets the rule not to ignore proc scopes if false" do
subject.ignore_procs = false rule = UnusedArgument.new
s = Source.new %( rule.ignore_procs = false
expect_issue rule, <<-CRYSTAL
->(a : Int32) {} ->(a : Int32) {}
) # ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
subject.catch(s).should_not be_valid CRYSTAL
end end
end end
end end

View file

@ -5,105 +5,118 @@ module Ameba::Rule::Lint
describe UnusedBlockArgument do describe UnusedBlockArgument do
it "doesn't report if it is an instance var argument" do it "doesn't report if it is an instance var argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class A class A
def initialize(&@callback) def initialize(&@callback)
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if anonymous" do it "doesn't report if anonymous" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(a, b, c, &) def method(a, b, c, &)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if argument name starts with a `_`" do it "doesn't report if argument name starts with a `_`" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(a, b, c, &_block) def method(a, b, c, &_block)
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if it is a block and used" do it "doesn't report if it is a block and used" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(a, b, c, &block) def method(a, b, c, &block)
block.call block.call
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "reports if block arg is not used" do it "reports if block arg is not used" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
def method(a, b, c, &block) def method(a, b, c, &block)
# ^^^^^ error: Unused block argument `block`. [...]
end end
) CRYSTAL
subject.catch(s).should_not be_valid
expect_correction source, <<-CRYSTAL
def method(a, b, c, &_block)
end
CRYSTAL
end end
it "reports if unused and there is yield" do it "reports if unused and there is yield" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
def method(a, b, c, &block) def method(a, b, c, &block)
# ^^^^^ error: Use `&` as an argument name to indicate that it won't be referenced.
3.times do |i| 3.times do |i|
i.try do i.try do
yield i yield i
end end
end end
end end
) CRYSTAL
subject.catch(s).should_not be_valid
expect_correction source, <<-CRYSTAL
def method(a, b, c, &)
3.times do |i|
i.try do
yield i
end
end
end
CRYSTAL
end end
it "doesn't report if anonymous and there is yield" do it "doesn't report if anonymous and there is yield" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
def method(a, b, c, &) def method(a, b, c, &)
yield 1 yield 1
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
it "doesn't report if variable is referenced implicitly" do it "doesn't report if variable is referenced implicitly" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
class Bar < Foo class Bar < Foo
def method(a, b, c, &block) def method(a, b, c, &block)
super super
end end
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
context "super" do context "super" do
it "reports if variable is not referenced implicitly by super" do it "reports if variable is not referenced implicitly by super" do
s = Source.new %( source = expect_issue subject, <<-CRYSTAL
class Bar < Foo class Bar < Foo
def method(a, b, c, &block) def method(a, b, c, &block)
# ^^^^^ error: Unused block argument `block`. [...]
super a, b, c super a, b, c
end end
end end
) CRYSTAL
subject.catch(s).should_not be_valid
s.issues.first.message.should eq "Unused block argument `block`. If it's necessary, " \ expect_correction source, <<-CRYSTAL
"use `_block` as an argument name to indicate " \ class Bar < Foo
"that it won't be used." def method(a, b, c, &_block)
super a, b, c
end
end
CRYSTAL
end end
end end
context "macro" do context "macro" do
it "doesn't report if it is a used macro block argument" do it "doesn't report if it is a used macro block argument" do
s = Source.new %( expect_no_issues subject, <<-CRYSTAL
macro my_macro(&block) macro my_macro(&block)
{% block %} {% block %}
end end
) CRYSTAL
subject.catch(s).should be_valid
end end
end end
end end

View file

@ -24,22 +24,5 @@ module Ameba::Rule::Lint
end end
CRYSTAL CRYSTAL
end end
it "reports rule, location and message" do
s = Source.new %(
case
when String
puts "hello"
when can_generate?
generate if can_generate?
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:5:15"
issue.end_location.to_s.should eq "source.cr:5:27"
issue.message.should eq "Useless condition in when detected"
end
end end
end end

View file

@ -17,7 +17,7 @@ module Ameba::Rule::Performance
it "reports if there is select followed by any? without a block" do it "reports if there is select followed by any? without a block" do
source = expect_issue subject, <<-CRYSTAL source = expect_issue subject, <<-CRYSTAL
[1, 2, 3].select { |e| e > 2 }.any? [1, 2, 3].select { |e| e > 2 }.any?
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `select {...}.any?` # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `select {...}.any?`
CRYSTAL CRYSTAL
expect_no_corrections source expect_no_corrections source
@ -32,7 +32,7 @@ module Ameba::Rule::Performance
it "reports if there is reject followed by any? without a block" do it "reports if there is reject followed by any? without a block" do
source = expect_issue subject, <<-CRYSTAL source = expect_issue subject, <<-CRYSTAL
[1, 2, 3].reject { |e| e > 2 }.any? [1, 2, 3].reject { |e| e > 2 }.any?
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?` # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?`
CRYSTAL CRYSTAL
expect_no_corrections source expect_no_corrections source
@ -46,8 +46,8 @@ module Ameba::Rule::Performance
end end
context "properties" do context "properties" do
it "allows to configure object_call_names" do it "#filter_names" do
rule = Rule::Performance::AnyAfterFilter.new rule = AnyAfterFilter.new
rule.filter_names = %w(select) rule.filter_names = %w(select)
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
@ -60,20 +60,11 @@ module Ameba::Rule::Performance
it "reports in macro scope" do it "reports in macro scope" do
source = expect_issue subject, <<-CRYSTAL source = expect_issue subject, <<-CRYSTAL
{{ [1, 2, 3].reject { |e| e > 2 }.any? }} {{ [1, 2, 3].reject { |e| e > 2 }.any? }}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?`
CRYSTAL
expect_no_corrections source
end
end
it "reports rule, pos and message" do
source = expect_issue subject, <<-CRYSTAL
[1, 2, 3].reject { |e| e > 2 }.any?
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?` # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?`
CRYSTAL CRYSTAL
expect_no_corrections source expect_no_corrections source
end end
end end
end
end end

View file

@ -14,14 +14,10 @@ module Ameba::Rule::Performance
end end
it "reports if there is any? call without a block nor argument" do it "reports if there is any? call without a block nor argument" do
source = expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].any? [1, 2, 3].any?
# ^^^^ error: Use `!{...}.empty?` instead of `{...}.any?` # ^^^^ error: Use `!{...}.empty?` instead of `{...}.any?`
CRYSTAL CRYSTAL
expect_correction source, <<-CRYSTAL
![1, 2, 3].empty?
CRYSTAL
end end
it "does not report if source is a spec" do it "does not report if source is a spec" do
@ -32,28 +28,11 @@ module Ameba::Rule::Performance
context "macro" do context "macro" do
it "reports in macro scope" do it "reports in macro scope" do
source = expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
{{ [1, 2, 3].any? }} {{ [1, 2, 3].any? }}
# ^^^^ error: Use `!{...}.empty?` instead of `{...}.any?` # ^^^^ error: Use `!{...}.empty?` instead of `{...}.any?`
CRYSTAL CRYSTAL
expect_correction source, <<-CRYSTAL
{{ ![1, 2, 3].empty? }}
CRYSTAL
end end
end end
it "reports rule, pos and message" do
source = Source.new path: "source.cr", code: %(
[1, 2, 3].any?
)
subject.catch(source).should_not be_valid
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:11"
issue.end_location.to_s.should eq "source.cr:1:14"
issue.message.should eq "Use `!{...}.empty?` instead of `{...}.any?`"
end
end end
end end

View file

@ -44,7 +44,7 @@ module Ameba::Rule::Performance
end end
context "properties" do context "properties" do
it "allows to configure `call_names`" do it "#call_names" do
rule = ChainedCallWithNoBang.new rule = ChainedCallWithNoBang.new
rule.call_names = %w(uniq) rule.call_names = %w(uniq)
@ -54,22 +54,6 @@ module Ameba::Rule::Performance
end end
end end
it "reports rule, pos and message" do
source = Source.new path: "source.cr", code: <<-CODE
[1, 2, 3].select { |e| e > 1 }.reverse
CODE
subject.catch(source).should_not be_valid
source.issues.size.should eq 1
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:32"
issue.end_location.to_s.should eq "source.cr:1:38"
issue.message.should eq "Use bang method variant `reverse!` after chained `select` call"
end
context "macro" do context "macro" do
it "doesn't report in macro scope" do it "doesn't report in macro scope" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL

View file

@ -19,7 +19,7 @@ module Ameba::Rule::Performance
it "reports if there is map followed by compact call" do it "reports if there is map followed by compact call" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
(1..3).map(&.itself).compact (1..3).map(&.itself).compact
# ^^^^^^^^^^^^^^^^^^^^^^ error: Use `compact_map {...}` instead of `map {...}.compact` # ^^^^^^^^^^^^^^^^^^^^^ error: Use `compact_map {...}` instead of `map {...}.compact`
CRYSTAL CRYSTAL
end end
@ -36,18 +36,5 @@ module Ameba::Rule::Performance
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
(1..3).map(&.itself).compact
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:8"
issue.end_location.to_s.should eq "source.cr:1:29"
issue.message.should eq "Use `compact_map {...}` instead of `map {...}.compact`"
end
end end
end end

View file

@ -17,7 +17,7 @@ module Ameba::Rule::Performance
it "reports if there is select followed by last" do it "reports if there is select followed by last" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].select { |e| e > 2 }.last [1, 2, 3].select { |e| e > 2 }.last
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last` # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last`
CRYSTAL CRYSTAL
end end
@ -30,14 +30,14 @@ module Ameba::Rule::Performance
it "reports if there is select followed by last?" do it "reports if there is select followed by last?" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].select { |e| e > 2 }.last? [1, 2, 3].select { |e| e > 2 }.last?
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last?` # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last?`
CRYSTAL CRYSTAL
end end
it "reports if there is select followed by first" do it "reports if there is select followed by first" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].select { |e| e > 2 }.first [1, 2, 3].select { |e| e > 2 }.first
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first` # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first`
CRYSTAL CRYSTAL
end end
@ -50,7 +50,7 @@ module Ameba::Rule::Performance
it "reports if there is select followed by first?" do it "reports if there is select followed by first?" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].select { |e| e > 2 }.first? [1, 2, 3].select { |e| e > 2 }.first?
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first?` # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first?`
CRYSTAL CRYSTAL
end end
@ -62,8 +62,8 @@ module Ameba::Rule::Performance
end end
context "properties" do context "properties" do
it "allows to configure object_call_names" do it "#filter_names" do
rule = Rule::Performance::FirstLastAfterFilter.new rule = FirstLastAfterFilter.new
rule.filter_names = %w(reject) rule.filter_names = %w(reject)
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
@ -72,21 +72,6 @@ module Ameba::Rule::Performance
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
[1, 2, 3].select { |e| e > 2 }.first
), "source.cr"
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:11"
issue.end_location.to_s.should eq "source.cr:1:37"
issue.message.should eq "Use `find {...}` instead of `select {...}.first`"
end
context "macro" do context "macro" do
it "doesn't report in macro scope" do it "doesn't report in macro scope" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL

View file

@ -13,7 +13,7 @@ module Ameba::Rule::Performance
it "reports if there is map followed by flatten call" do it "reports if there is map followed by flatten call" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
%w[Alice Bob].map(&.chars).flatten %w[Alice Bob].map(&.chars).flatten
# ^^^^^^^^^^^^^^^^^^^^^ error: Use `flat_map {...}` instead of `map {...}.flatten` # ^^^^^^^^^^^^^^^^^^^^ error: Use `flat_map {...}` instead of `map {...}.flatten`
CRYSTAL CRYSTAL
end end
@ -30,18 +30,5 @@ module Ameba::Rule::Performance
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
%w[Alice Bob].map(&.chars).flatten
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:15"
issue.end_location.to_s.should eq "source.cr:1:35"
issue.message.should eq "Use `flat_map {...}` instead of `map {...}.flatten`"
end
end end
end end

View file

@ -14,7 +14,7 @@ module Ameba::Rule::Performance
it "reports if there is map followed by sum without a block" do it "reports if there is map followed by sum without a block" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
(1..3).map(&.to_u64).sum (1..3).map(&.to_u64).sum
# ^^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum` # ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
CRYSTAL CRYSTAL
end end
@ -27,14 +27,14 @@ module Ameba::Rule::Performance
it "reports if there is map followed by sum without a block (with argument)" do it "reports if there is map followed by sum without a block (with argument)" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
(1..3).map(&.to_u64).sum(0) (1..3).map(&.to_u64).sum(0)
# ^^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum` # ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
CRYSTAL CRYSTAL
end end
it "reports if there is map followed by sum with a block" do it "reports if there is map followed by sum with a block" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
(1..3).map(&.to_u64).sum(&.itself) (1..3).map(&.to_u64).sum(&.itself)
# ^^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum` # ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
CRYSTAL CRYSTAL
end end
@ -45,18 +45,5 @@ module Ameba::Rule::Performance
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
(1..3).map(&.to_u64).sum
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:8"
issue.end_location.to_s.should eq "source.cr:1:25"
issue.message.should eq "Use `sum {...}` instead of `map {...}.sum`"
end
end end
end end

View file

@ -19,7 +19,7 @@ module Ameba::Rule::Performance
it "reports if there is a select followed by size" do it "reports if there is a select followed by size" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].select { |e| e > 2 }.size [1, 2, 3].select { |e| e > 2 }.size
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `select {...}.size`. # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `select {...}.size`.
CRYSTAL CRYSTAL
end end
@ -32,20 +32,20 @@ module Ameba::Rule::Performance
it "reports if there is a reject followed by size" do it "reports if there is a reject followed by size" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].reject { |e| e < 2 }.size [1, 2, 3].reject { |e| e < 2 }.size
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`. # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`.
CRYSTAL CRYSTAL
end end
it "reports if a block shorthand used" do it "reports if a block shorthand used" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
[1, 2, 3].reject(&.empty?).size [1, 2, 3].reject(&.empty?).size
# ^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`. # ^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`.
CRYSTAL CRYSTAL
end end
context "properties" do context "properties" do
it "allows to configure object caller names" do it "#filter_names" do
rule = Rule::Performance::SizeAfterFilter.new rule = SizeAfterFilter.new
rule.filter_names = %w(select) rule.filter_names = %w(select)
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
@ -61,18 +61,5 @@ module Ameba::Rule::Performance
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
s = Source.new %(
lines.split("\n").reject(&.empty?).size
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:4"
issue.end_location.to_s.should eq "source.cr:2:25"
issue.message.should eq "Use `count {...}` instead of `reject {...}.size`."
end
end end
end end

View file

@ -3,10 +3,10 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Style::ConstantNames.new subject = Rule::Style::ConstantNames.new
private def it_reports_constant(name, value, expected) private def it_reports_constant(name, value, expected, *, file = __FILE__, line = __LINE__)
it "reports constant name #{expected}" do it "reports constant name #{expected}", file, line do
rule = Rule::Style::ConstantNames.new rule = Rule::Style::ConstantNames.new
expect_issue rule, <<-CRYSTAL, name: name expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
%{name} = #{value} %{name} = #{value}
# ^{name} error: Constant name should be screaming-cased: #{expected}, not #{name} # ^{name} error: Constant name should be screaming-cased: #{expected}, not #{name}
CRYSTAL CRYSTAL
@ -17,7 +17,7 @@ module Ameba
it "passes if type names are screaming-cased" do it "passes if type names are screaming-cased" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL
LUCKY_NUMBERS = [3, 7, 11] LUCKY_NUMBERS = [3, 7, 11]
DOCUMENTATION_URL = "http://crystal-lang.org/docs" DOCUMENTATION_URL = "https://crystal-lang.org/docs"
Int32 Int32
@ -37,19 +37,5 @@ module Ameba
# it_reports_constant "MyBadConstant", "1", "MYBADCONSTANT" # it_reports_constant "MyBadConstant", "1", "MYBADCONSTANT"
it_reports_constant "Wrong_NAME", "2", "WRONG_NAME" it_reports_constant "Wrong_NAME", "2", "WRONG_NAME"
it_reports_constant "Wrong_Name", "3", "WRONG_NAME" it_reports_constant "Wrong_Name", "3", "WRONG_NAME"
it "reports rule, pos and message" do
s = Source.new %(
Const_Name = 1
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:10"
issue.message.should eq(
"Constant name should be screaming-cased: CONST_NAME, not Const_Name"
)
end
end end
end end

View file

@ -3,11 +3,11 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Style::GuardClause.new subject = Rule::Style::GuardClause.new
def it_reports_body(body, *, line = __LINE__) private def it_reports_body(body, *, file = __FILE__, line = __LINE__)
rule = Rule::Style::GuardClause.new rule = Rule::Style::GuardClause.new
it "reports an issue if method body is if / unless without else" do it "reports an issue if method body is if / unless without else", file, line do
source = expect_issue rule, <<-CRYSTAL, line: line source = expect_issue rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
# ^^ error: Use a guard clause (`return unless something`) instead of wrapping the code inside a conditional expression. # ^^ error: Use a guard clause (`return unless something`) instead of wrapping the code inside a conditional expression.
@ -23,7 +23,7 @@ module Ameba
end end
CRYSTAL CRYSTAL
expect_correction source, <<-CRYSTAL, line: line expect_correction source, <<-CRYSTAL, file: file, line: line
def func def func
return unless something return unless something
#{body} #{body}
@ -38,8 +38,8 @@ module Ameba
CRYSTAL CRYSTAL
end end
it "reports an issue if method body ends with if / unless without else" do it "reports an issue if method body ends with if / unless without else", file, line do
source = expect_issue rule, <<-CRYSTAL, line: line source = expect_issue rule, <<-CRYSTAL, file: file, line: line
def func def func
test test
if something if something
@ -57,7 +57,7 @@ module Ameba
end end
CRYSTAL CRYSTAL
expect_correction source, <<-CRYSTAL, line: line expect_correction source, <<-CRYSTAL, file: file, line: line
def func def func
test test
return unless something return unless something
@ -75,11 +75,11 @@ module Ameba
end end
end end
def it_reports_control_expression(kw, *, line = __LINE__) private def it_reports_control_expression(kw, *, file = __FILE__, line = __LINE__)
rule = Rule::Style::GuardClause.new rule = Rule::Style::GuardClause.new
it "reports an issue with #{kw} in the if branch" do it "reports an issue with #{kw} in the if branch", file, line do
source = expect_issue rule, <<-CRYSTAL, line: line source = expect_issue rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
# ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression. # ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression.
@ -90,11 +90,11 @@ module Ameba
end end
CRYSTAL CRYSTAL
expect_no_corrections source, line: line expect_no_corrections source, file: file, line: line
end end
it "reports an issue with #{kw} in the else branch" do it "reports an issue with #{kw} in the else branch", file, line do
source = expect_issue rule, <<-CRYSTAL, line: line source = expect_issue rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
# ^^ error: Use a guard clause (`#{kw} unless something`) instead of wrapping the code inside a conditional expression. # ^^ error: Use a guard clause (`#{kw} unless something`) instead of wrapping the code inside a conditional expression.
@ -105,11 +105,11 @@ module Ameba
end end
CRYSTAL CRYSTAL
expect_no_corrections source, line: line expect_no_corrections source, file: file, line: line
end end
it "doesn't report an issue if condition has multiple lines" do it "doesn't report an issue if condition has multiple lines", file, line do
expect_no_issues rule, <<-CRYSTAL, line: line expect_no_issues rule, <<-CRYSTAL, file: file, line: line
def func def func
if something && if something &&
something_else something_else
@ -121,8 +121,8 @@ module Ameba
CRYSTAL CRYSTAL
end end
it "does not report an issue if #{kw} is inside elsif" do it "does not report an issue if #{kw} is inside elsif", file, line do
expect_no_issues rule, <<-CRYSTAL, line: line expect_no_issues rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
a a
@ -133,8 +133,8 @@ module Ameba
CRYSTAL CRYSTAL
end end
it "does not report an issue if #{kw} is inside if..elsif..else..end" do it "does not report an issue if #{kw} is inside if..elsif..else..end", file, line do
expect_no_issues rule, <<-CRYSTAL, line: line expect_no_issues rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
a a
@ -147,8 +147,8 @@ module Ameba
CRYSTAL CRYSTAL
end end
it "doesn't report an issue if control flow expr has multiple lines" do it "doesn't report an issue if control flow expr has multiple lines", file, line do
expect_no_issues rule, <<-CRYSTAL, line: line expect_no_issues rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
#{kw} \\ #{kw} \\
@ -161,8 +161,8 @@ module Ameba
CRYSTAL CRYSTAL
end end
it "reports an issue if non-control-flow branch has multiple lines" do it "reports an issue if non-control-flow branch has multiple lines", file, line do
source = expect_issue rule, <<-CRYSTAL, line: line source = expect_issue rule, <<-CRYSTAL, file: file, line: line
def func def func
if something if something
# ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression. # ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression.
@ -174,7 +174,7 @@ module Ameba
end end
CRYSTAL CRYSTAL
expect_no_corrections source, line: line expect_no_corrections source, file: file, line: line
end end
end end

View file

@ -42,9 +42,10 @@ module Ameba::Rule::Style
end end
context "properties" do context "properties" do
it "allows to configure filter_names" do it "#filter_names" do
rule = IsAFilter.new rule = IsAFilter.new
rule.filter_names = %w(select) rule.filter_names = %w(select)
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
[1, 2, nil].reject(&.nil?) [1, 2, nil].reject(&.nil?)
CRYSTAL CRYSTAL
@ -58,20 +59,5 @@ module Ameba::Rule::Style
CRYSTAL CRYSTAL
end end
end end
it "reports rule, pos and message" do
source = Source.new path: "source.cr", code: %(
[1, 2, nil].reject(&.nil?)
)
subject.catch(source).should_not be_valid
source.issues.size.should eq 1
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:13"
issue.end_location.to_s.should eq "source.cr:1:26"
issue.message.should eq "Use `reject(Nil)` instead of `reject {...}`"
end
end end
end end

View file

@ -34,19 +34,5 @@ module Ameba::Rule::Style
a.nil? a.nil?
CRYSTAL CRYSTAL
end end
it "reports rule, location and message" do
s = Source.new %(
nil.is_a? Nil
), "source.cr"
subject.catch(s).should_not be_valid
s.issues.size.should eq 1
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:11"
issue.end_location.to_s.should eq "source.cr:1:13"
issue.message.should eq IsANil::MSG
end
end end
end end

View file

@ -3,12 +3,12 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Style::LargeNumbers.new subject = Rule::Style::LargeNumbers.new
private def it_transforms(number, expected) private def it_transforms(number, expected, *, file = __FILE__, line = __LINE__)
it "transforms large number #{number}" do it "transforms large number #{number}", file, line do
rule = Rule::Style::LargeNumbers.new rule = Rule::Style::LargeNumbers.new
rule.int_min_digits = 5 rule.int_min_digits = 5
source = expect_issue rule, <<-CRYSTAL, number: number source = expect_issue rule, <<-CRYSTAL, number: number, file: file, line: line
number = %{number} number = %{number}
# ^{number} error: Large numbers should be written with underscores: #{expected} # ^{number} error: Large numbers should be written with underscores: #{expected}
CRYSTAL CRYSTAL
@ -97,10 +97,12 @@ module Ameba
it_transforms "10000_i16", "10_000_i16" it_transforms "10000_i16", "10_000_i16"
it_transforms "10000_i32", "10_000_i32" it_transforms "10000_i32", "10_000_i32"
it_transforms "10000_i64", "10_000_i64" it_transforms "10000_i64", "10_000_i64"
it_transforms "10000_i128", "10_000_i128"
it_transforms "10000_u16", "10_000_u16" it_transforms "10000_u16", "10_000_u16"
it_transforms "10000_u32", "10_000_u32" it_transforms "10000_u32", "10_000_u32"
it_transforms "10000_u64", "10_000_u64" it_transforms "10000_u64", "10_000_u64"
it_transforms "10000_u128", "10_000_u128"
it_transforms "123456_f32", "123_456_f32" it_transforms "123456_f32", "123_456_f32"
it_transforms "123456_f64", "123_456_f64" it_transforms "123456_f64", "123_456_f64"
@ -117,20 +119,8 @@ module Ameba
it_transforms "3.001234", "3.001_234" it_transforms "3.001234", "3.001_234"
it_transforms "3.0012345", "3.001_234_5" it_transforms "3.0012345", "3.001_234_5"
it "reports rule, pos and message" do
s = Source.new %q(
1200000
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:7"
issue.message.should match /1_200_000/
end
context "properties" do context "properties" do
it "allows to configure integer min digits" do it "#int_min_digits" do
rule = Rule::Style::LargeNumbers.new rule = Rule::Style::LargeNumbers.new
rule.int_min_digits = 10 rule.int_min_digits = 10
expect_no_issues rule, %q(1200000) expect_no_issues rule, %q(1200000)

View file

@ -3,10 +3,10 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Style::MethodNames.new subject = Rule::Style::MethodNames.new
private def it_reports_method_name(name, expected) private def it_reports_method_name(name, expected, *, file = __FILE__, line = __LINE__)
it "reports method name #{expected}" do it "reports method name #{expected}", file, line do
rule = Rule::Style::MethodNames.new rule = Rule::Style::MethodNames.new
expect_issue rule, <<-CRYSTAL, name: name expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
def %{name}; end def %{name}; end
# ^{name} error: Method name should be underscore-cased: #{expected}, not %{name} # ^{name} error: Method name should be underscore-cased: #{expected}, not %{name}
CRYSTAL CRYSTAL
@ -38,20 +38,5 @@ module Ameba
it_reports_method_name "firstName", "first_name" it_reports_method_name "firstName", "first_name"
it_reports_method_name "date_of_Birth", "date_of_birth" it_reports_method_name "date_of_Birth", "date_of_birth"
it_reports_method_name "homepageURL", "homepage_url" it_reports_method_name "homepageURL", "homepage_url"
it "reports rule, pos and message" do
s = Source.new %(
def bad_Name(a)
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:5"
issue.end_location.to_s.should eq "source.cr:1:12"
issue.message.should eq(
"Method name should be underscore-cased: bad_name, not bad_Name"
)
end
end end
end end

View file

@ -53,16 +53,5 @@ module Ameba::Rule::Style
end end
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
s = Source.new ":nok unless !s.empty?", "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:21"
issue.message.should eq "Avoid negated conditions in unless blocks"
end
end end
end end

View file

@ -43,7 +43,7 @@ module Ameba::Rule::Style
end end
context "properties" do context "properties" do
context "#exclude_ternary=" do context "#exclude_ternary" do
it "skips ternary control expressions by default" do it "skips ternary control expressions by default" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL
(foo > bar) ? true : false (foo > bar) ? true : false
@ -51,7 +51,7 @@ module Ameba::Rule::Style
end end
it "allows to configure assignments" do it "allows to configure assignments" do
rule = Rule::Style::ParenthesesAroundCondition.new rule = ParenthesesAroundCondition.new
rule.exclude_ternary = false rule.exclude_ternary = false
expect_issue rule, <<-CRYSTAL expect_issue rule, <<-CRYSTAL
@ -75,7 +75,7 @@ module Ameba::Rule::Style
end end
end end
context "#allow_safe_assignment=" do context "#allow_safe_assignment" do
it "reports assignments by default" do it "reports assignments by default" do
expect_issue subject, <<-CRYSTAL expect_issue subject, <<-CRYSTAL
if (foo = @foo) if (foo = @foo)
@ -98,7 +98,7 @@ module Ameba::Rule::Style
end end
it "allows to configure assignments" do it "allows to configure assignments" do
rule = Rule::Style::ParenthesesAroundCondition.new rule = ParenthesesAroundCondition.new
rule.allow_safe_assignment = true rule.allow_safe_assignment = true
expect_issue rule, <<-CRYSTAL expect_issue rule, <<-CRYSTAL

View file

@ -27,24 +27,6 @@ module Ameba::Rule::Style
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
s = Source.new %q(
class Image
def is_valid?(x)
true
end
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:3"
issue.end_location.to_s.should eq "source.cr:4:5"
issue.message.should eq(
"Favour method name 'valid?' over 'is_valid?'")
end
it "ignores if alternative name isn't valid syntax" do it "ignores if alternative name isn't valid syntax" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL
class Image class Image

View file

@ -51,7 +51,7 @@ module Ameba::Rule::Style
expect_issue subject, <<-CRYSTAL, call: {{ call }} expect_issue subject, <<-CRYSTAL, call: {{ call }}
class Foo class Foo
%{call} foo : Bool = true %{call} foo : Bool = true
_{call} # ^ error: Consider using '%{call}?' for 'foo' _{call} # ^^^ error: Consider using '%{call}?' for 'foo'
end end
CRYSTAL CRYSTAL
end end
@ -60,7 +60,7 @@ module Ameba::Rule::Style
expect_issue subject, <<-CRYSTAL, call: {{ call }} expect_issue subject, <<-CRYSTAL, call: {{ call }}
class Foo class Foo
%{call} foo : Bool %{call} foo : Bool
_{call} # ^ error: Consider using '%{call}?' for 'foo' _{call} # ^^^ error: Consider using '%{call}?' for 'foo'
def initialize(@foo = true) def initialize(@foo = true)
end end

View file

@ -294,24 +294,5 @@ module Ameba::Rule::Style
} }
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
s = Source.new %q(
def method
begin
open_connection
ensure
close_connection
end
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:2:3"
issue.end_location.to_s.should eq "source.cr:2:7"
issue.message.should eq "Redundant `begin` block detected"
end
end end
end end

View file

@ -201,7 +201,7 @@ module Ameba::Rule::Style
end end
context "properties" do context "properties" do
context "#allow_multi_next=" do context "#allow_multi_next" do
it "allows multi next statements by default" do it "allows multi next statements by default" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL
block do |a, b| block do |a, b|
@ -211,7 +211,7 @@ module Ameba::Rule::Style
end end
it "allows to configure multi next statements" do it "allows to configure multi next statements" do
rule = Rule::Style::RedundantNext.new rule = RedundantNext.new
rule.allow_multi_next = false rule.allow_multi_next = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
block do |a, b| block do |a, b|
@ -238,7 +238,7 @@ module Ameba::Rule::Style
end end
it "allows to configure empty next statements" do it "allows to configure empty next statements" do
rule = Rule::Style::RedundantNext.new rule = RedundantNext.new
rule.allow_empty_next = false rule.allow_empty_next = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
block do block do

View file

@ -284,7 +284,7 @@ module Ameba::Rule::Style
end end
context "properties" do context "properties" do
context "#allow_multi_return=" do context "#allow_multi_return" do
it "allows multi returns by default" do it "allows multi returns by default" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL
def method(a, b) def method(a, b)
@ -294,7 +294,7 @@ module Ameba::Rule::Style
end end
it "allows to configure multi returns" do it "allows to configure multi returns" do
rule = Rule::Style::RedundantReturn.new rule = RedundantReturn.new
rule.allow_multi_return = false rule.allow_multi_return = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
def method(a, b) def method(a, b)
@ -321,7 +321,7 @@ module Ameba::Rule::Style
end end
it "allows to configure empty returns" do it "allows to configure empty returns" do
rule = Rule::Style::RedundantReturn.new rule = RedundantReturn.new
rule.allow_empty_return = false rule.allow_empty_return = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
def method def method

View file

@ -3,10 +3,10 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Style::TypeNames.new subject = Rule::Style::TypeNames.new
private def it_reports_name(type, name, expected) private def it_reports_name(type, name, expected, *, file = __FILE__, line = __LINE__)
it "reports type name #{expected}" do it "reports type name #{expected}", file, line do
rule = Rule::Style::TypeNames.new rule = Rule::Style::TypeNames.new
expect_issue rule, <<-CRYSTAL, type: type, name: name expect_issue rule, <<-CRYSTAL, type: type, name: name, file: file, line: line
%{type} %{name}; end %{type} %{name}; end
# ^{type}^{name}^^^^ error: Type name should be camelcased: #{expected}, but it was %{name} # ^{type}^{name}^^^^ error: Type name should be camelcased: #{expected}, but it was %{name}
CRYSTAL CRYSTAL
@ -49,20 +49,5 @@ module Ameba
# ^{} error: Type name should be camelcased: NumericValue, but it was Numeric_value # ^{} error: Type name should be camelcased: NumericValue, but it was Numeric_value
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
s = Source.new %(
class My_class
end
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:2:3"
issue.message.should eq(
"Type name should be camelcased: MyClass, but it was My_class"
)
end
end end
end end

View file

@ -13,7 +13,7 @@ module Ameba::Rule::Style
end end
it "fails if unless has else" do it "fails if unless has else" do
expect_issue subject, <<-CRYSTAL source = expect_issue subject, <<-CRYSTAL
unless something unless something
# ^^^^^^^^^^^^^^ error: Favour if over unless with else # ^^^^^^^^^^^^^^ error: Favour if over unless with else
:one :one
@ -21,24 +21,14 @@ module Ameba::Rule::Style
:two :two
end end
CRYSTAL CRYSTAL
end
it "reports rule, pos and message" do expect_correction source, <<-CRYSTAL
s = Source.new %( if something
unless something
:one
else
:two :two
else
:one
end end
), "source.cr" CRYSTAL
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.should_not be_nil
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:5:3"
issue.message.should eq "Favour if over unless with else"
end end
end end
end end

View file

@ -3,10 +3,10 @@ require "../../../spec_helper"
module Ameba module Ameba
subject = Rule::Style::VariableNames.new subject = Rule::Style::VariableNames.new
private def it_reports_var_name(name, value, expected) private def it_reports_var_name(name, value, expected, *, file = __FILE__, line = __LINE__)
it "reports variable name #{expected}" do it "reports variable name #{expected}", file, line do
rule = Rule::Style::VariableNames.new rule = Rule::Style::VariableNames.new
expect_issue rule, <<-CRYSTAL, name: name expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
%{name} = #{value} %{name} = #{value}
# ^{name} error: Var name should be underscore-cased: #{expected}, not %{name} # ^{name} error: Var name should be underscore-cased: #{expected}, not %{name}
CRYSTAL CRYSTAL
@ -62,19 +62,5 @@ module Ameba
end end
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
s = Source.new %(
badName = "Yeah"
), "source.cr"
subject.catch(s).should_not be_valid
issue = s.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:1"
issue.end_location.to_s.should eq "source.cr:1:7"
issue.message.should eq(
"Var name should be underscore-cased: bad_name, not badName"
)
end
end end
end end

View file

@ -64,10 +64,12 @@ module Ameba::Rule::Style
context "properties" do context "properties" do
it "#exclude_calls_with_block" do it "#exclude_calls_with_block" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_calls_with_block = true rule.exclude_calls_with_block = true
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
(1..3).in_groups_of(1) { |i| i.map(&.to_s) } (1..3).in_groups_of(1) { |i| i.map(&.to_s) }
CRYSTAL CRYSTAL
rule.exclude_calls_with_block = false rule.exclude_calls_with_block = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
(1..3).in_groups_of(1) { |i| i.map(&.to_s) } (1..3).in_groups_of(1) { |i| i.map(&.to_s) }
@ -81,12 +83,14 @@ module Ameba::Rule::Style
it "#exclude_multiple_line_blocks" do it "#exclude_multiple_line_blocks" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_multiple_line_blocks = true rule.exclude_multiple_line_blocks = true
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
(1..3).any? do |i| (1..3).any? do |i|
i.odd? i.odd?
end end
CRYSTAL CRYSTAL
rule.exclude_multiple_line_blocks = false rule.exclude_multiple_line_blocks = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
(1..3).any? do |i| (1..3).any? do |i|
@ -102,12 +106,14 @@ module Ameba::Rule::Style
it "#exclude_prefix_operators" do it "#exclude_prefix_operators" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_prefix_operators = true rule.exclude_prefix_operators = true
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
(1..3).sum { |i| +i } (1..3).sum { |i| +i }
(1..3).sum { |i| -i } (1..3).sum { |i| -i }
(1..3).sum { |i| ~i } (1..3).sum { |i| ~i }
CRYSTAL CRYSTAL
rule.exclude_prefix_operators = false rule.exclude_prefix_operators = false
rule.exclude_operators = false rule.exclude_operators = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
@ -128,10 +134,12 @@ module Ameba::Rule::Style
it "#exclude_operators" do it "#exclude_operators" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_operators = true rule.exclude_operators = true
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
(1..3).sum { |i| i * 2 } (1..3).sum { |i| i * 2 }
CRYSTAL CRYSTAL
rule.exclude_operators = false rule.exclude_operators = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
(1..3).sum { |i| i * 2 } (1..3).sum { |i| i * 2 }
@ -145,10 +153,12 @@ module Ameba::Rule::Style
it "#exclude_setters" do it "#exclude_setters" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_setters = true rule.exclude_setters = true
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
Char::Reader.new("abc").tap { |reader| reader.pos = 0 } Char::Reader.new("abc").tap { |reader| reader.pos = 0 }
CRYSTAL CRYSTAL
rule.exclude_setters = false rule.exclude_setters = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
Char::Reader.new("abc").tap { |reader| reader.pos = 0 } Char::Reader.new("abc").tap { |reader| reader.pos = 0 }
@ -163,12 +173,14 @@ module Ameba::Rule::Style
it "#max_line_length" do it "#max_line_length" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_multiple_line_blocks = false rule.exclude_multiple_line_blocks = false
rule.max_line_length = 60 rule.max_line_length = 60
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
(1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i| (1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i|
i.to_s.reverse.strip.blank? i.to_s.reverse.strip.blank?
end end
CRYSTAL CRYSTAL
rule.max_line_length = nil rule.max_line_length = nil
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
(1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i| (1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i|
@ -184,10 +196,12 @@ module Ameba::Rule::Style
it "#max_length" do it "#max_length" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.max_length = 30 rule.max_length = 30
expect_no_issues rule, <<-CRYSTAL expect_no_issues rule, <<-CRYSTAL
(1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? } (1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? }
CRYSTAL CRYSTAL
rule.max_length = nil rule.max_length = nil
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
(1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? } (1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? }
@ -216,6 +230,7 @@ module Ameba::Rule::Style
it "reports call args and named_args" do it "reports call args and named_args" do
rule = VerboseBlock.new rule = VerboseBlock.new
rule.exclude_operators = false rule.exclude_operators = false
source = expect_issue rule, <<-CRYSTAL source = expect_issue rule, <<-CRYSTAL
(1..3).map { |i| i.to_s[start: 0.to_i64, count: 3]? } (1..3).map { |i| i.to_s[start: 0.to_i64, count: 3]? }
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[start: 0.to_i64, count: 3]?)` # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[start: 0.to_i64, count: 3]?)`
@ -258,20 +273,5 @@ module Ameba::Rule::Style
(1..3).join(separator: '.', &.to_s) (1..3).join(separator: '.', &.to_s)
CRYSTAL CRYSTAL
end end
it "reports rule, pos and message" do
source = Source.new path: "source.cr", code: %(
(1..3).any? { |i| i.odd? }
)
subject.catch(source).should_not be_valid
source.issues.size.should eq 1
issue = source.issues.first
issue.rule.should_not be_nil
issue.location.to_s.should eq "source.cr:1:8"
issue.end_location.to_s.should eq "source.cr:1:26"
issue.message.should eq "Use short block notation instead: `any?(&.odd?)`"
end
end end
end end

View file

@ -65,6 +65,7 @@ module Ameba
rules = [AtoAA.new] of Rule::Base rules = [AtoAA.new] of Rule::Base
source = Source.new "class A; end", "source.cr" source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoAA" message = "Infinite loop in source.cr caused by Ameba/AtoAA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end end
@ -95,11 +96,11 @@ module Ameba
it "does not run other rules" do it "does not run other rules" do
rules = [Rule::Lint::Syntax.new, Rule::Style::ConstantNames.new] of Rule::Base rules = [Rule::Lint::Syntax.new, Rule::Style::ConstantNames.new] of Rule::Base
source = Source.new %q( source = Source.new <<-CRYSTAL
MyBadConstant = 1 MyBadConstant = 1
when my_bad_syntax when my_bad_syntax
) CRYSTAL
Runner.new(rules, [source], formatter, default_severity).run Runner.new(rules, [source], formatter, default_severity).run
source.should_not be_valid source.should_not be_valid
@ -110,9 +111,9 @@ module Ameba
context "unneeded disables" do context "unneeded disables" do
it "reports an issue if such disable exists" do it "reports an issue if such disable exists" do
rules = [Rule::Lint::UnneededDisableDirective.new] of Rule::Base rules = [Rule::Lint::UnneededDisableDirective.new] of Rule::Base
source = Source.new %( source = Source.new <<-CRYSTAL
a = 1 # ameba:disable LineLength a = 1 # ameba:disable LineLength
) CRYSTAL
Runner.new(rules, [source], formatter, default_severity).run Runner.new(rules, [source], formatter, default_severity).run
source.should_not be_valid source.should_not be_valid
@ -134,9 +135,7 @@ module Ameba
it "writes the explanation if sources are not valid and location found" do it "writes the explanation if sources are not valid and location found" do
io.clear io.clear
rules = [ErrorRule.new] of Rule::Base rules = [ErrorRule.new] of Rule::Base
source = Source.new %( source = Source.new "a = 1", "source.cr"
a = 1
), "source.cr"
runner = Runner.new(rules, [source], formatter, default_severity).run runner = Runner.new(rules, [source], formatter, default_severity).run
runner.explain({file: "source.cr", line: 1, column: 1}, io) runner.explain({file: "source.cr", line: 1, column: 1}, io)
@ -146,9 +145,7 @@ module Ameba
it "writes nothing if sources are not valid and location is not found" do it "writes nothing if sources are not valid and location is not found" do
io.clear io.clear
rules = [ErrorRule.new] of Rule::Base rules = [ErrorRule.new] of Rule::Base
source = Source.new %( source = Source.new "a = 1", "source.cr"
a = 1
), "source.cr"
runner = Runner.new(rules, [source], formatter, default_severity).run runner = Runner.new(rules, [source], formatter, default_severity).run
runner.explain({file: "source.cr", line: 1, column: 2}, io) runner.explain({file: "source.cr", line: 1, column: 2}, io)
@ -167,10 +164,9 @@ module Ameba
it "returns false if there are invalid sources" do it "returns false if there are invalid sources" do
rules = Rule.rules.map &.new.as(Rule::Base) rules = Rule.rules.map &.new.as(Rule::Base)
s = Source.new %q( source = Source.new "WrongConstant = 5"
WrongConstant = 5
) Runner.new(rules, [source], formatter, default_severity).run.success?.should be_false
Runner.new(rules, [s], formatter, default_severity).run.success?.should be_false
end end
it "depends on the level of severity" do it "depends on the level of severity" do
@ -184,11 +180,11 @@ module Ameba
it "returns false if issue is disabled" do it "returns false if issue is disabled" do
rules = [NamedRule.new] of Rule::Base rules = [NamedRule.new] of Rule::Base
source = Source.new %( source = Source.new <<-CRYSTAL
def foo def foo
bar = 1 # ameba:disable #{NamedRule.name} bar = 1 # ameba:disable #{NamedRule.name}
end end
) CRYSTAL
source.add_issue NamedRule.new, location: {2, 1}, source.add_issue NamedRule.new, location: {2, 1},
message: "Useless assignment" message: "Useless assignment"
@ -205,6 +201,7 @@ module Ameba
rules = [AtoB.new, BtoA.new] rules = [AtoB.new, BtoA.new]
source = Source.new "class A; end", "source.cr" source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA" message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end end
@ -214,11 +211,12 @@ module Ameba
context "if there are multiple offenses in an inspected file" do context "if there are multiple offenses in an inspected file" do
it "aborts because of an infinite loop" do it "aborts because of an infinite loop" do
rules = [AtoB.new, BtoA.new] rules = [AtoB.new, BtoA.new]
source = Source.new %( source = Source.new <<-CRYSTAL, "source.cr"
class A; end class A; end
class A_A; end class A_A; end
), "source.cr" CRYSTAL
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA" message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end end
@ -231,6 +229,7 @@ module Ameba
rules = [ClassToModule.new, ModuleToClass.new, AtoB.new, BtoA.new] rules = [ClassToModule.new, ModuleToClass.new, AtoB.new, BtoA.new]
source = Source.new "class A_A; end", "source.cr" source = Source.new "class A_A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/ClassToModule, Ameba/AtoB -> Ameba/ModuleToClass, Ameba/BtoA" message = "Infinite loop in source.cr caused by Ameba/ClassToModule, Ameba/AtoB -> Ameba/ModuleToClass, Ameba/BtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end end
@ -242,6 +241,7 @@ module Ameba
rules = [AtoB.new, BtoC.new, CtoA.new] rules = [AtoB.new, BtoC.new, CtoA.new]
source = Source.new "class A; end", "source.cr" source = Source.new "class A; end", "source.cr"
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoC -> Ameba/CtoA" message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoC -> Ameba/CtoA"
expect_raises(Runner::InfiniteCorrectionLoopError, message) do expect_raises(Runner::InfiniteCorrectionLoopError, message) do
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
end end

View file

@ -4,46 +4,70 @@ module Ameba
describe Source do describe Source do
describe ".new" do describe ".new" do
it "allows to create a source by code and path" do it "allows to create a source by code and path" do
s = Source.new("code", "path") source = Source.new "code", "path"
s.path.should eq "path" source.path.should eq "path"
s.code.should eq "code" source.code.should eq "code"
s.lines.should eq ["code"] source.lines.should eq ["code"]
end end
end end
describe "#fullpath" do describe "#fullpath" do
it "returns a relative path of the source" do it "returns a relative path of the source" do
s = Source.new "", "./source_spec.cr" source = Source.new "", "./source_spec.cr"
s.fullpath.should contain "source_spec.cr" source.fullpath.should contain "source_spec.cr"
end end
it "returns fullpath if path is blank" do it "returns fullpath if path is blank" do
s = Source.new "", "" source = Source.new "", ""
s.fullpath.should_not be_nil source.fullpath.should_not be_nil
end end
end end
describe "#spec?" do describe "#spec?" do
it "returns true if the source is a spec file" do it "returns true if the source is a spec file" do
s = Source.new "", "./source_spec.cr" source = Source.new "", "./source_spec.cr"
s.spec?.should be_true source.spec?.should be_true
end end
it "returns false if the source is not a spec file" do it "returns false if the source is not a spec file" do
s = Source.new "", "./source.cr" source = Source.new "", "./source.cr"
s.spec?.should be_false source.spec?.should be_false
end end
end end
describe "#matches_path?" do describe "#matches_path?" do
it "returns true if source's path is matched" do it "returns true if source's path is matched" do
s = Source.new "", "source.cr" source = Source.new "", "source.cr"
s.matches_path?("source.cr").should be_true source.matches_path?("source.cr").should be_true
end end
it "returns false if source's path is not matched" do it "returns false if source's path is not matched" do
s = Source.new "", "source.cr" source = Source.new "", "source.cr"
s.matches_path?("new_source.cr").should be_false source.matches_path?("new_source.cr").should be_false
end
end
describe "#pos" do
it "works" do
source = Source.new <<-CRYSTAL
foo
bar
fizz
buzz
CRYSTAL
location = Crystal::Location.new("", 2, 1)
end_location = Crystal::Location.new("", 3, 4)
range = Range.new(
source.pos(location),
source.pos(end_location, end: true),
exclusive: true
)
source.code[range].should eq <<-CRYSTAL
bar
fizz
CRYSTAL
end end
end end
end end

View file

@ -1,13 +1,13 @@
require "../spec_helper" require "../spec_helper"
module Ameba module Ameba
private def it_tokenizes(str, expected) private def it_tokenizes(str, expected, *, file = __FILE__, line = __LINE__)
it "tokenizes #{str}" do it "tokenizes #{str}", file, line do
([] of String).tap do |token_types| %w[].tap do |token_types|
Tokenizer.new(Source.new str, normalize: false) Tokenizer.new(Source.new str, normalize: false)
.run { |token| token_types << token.type.to_s } .run { |token| token_types << token.type.to_s }
.should be_true .should be_true
end.should eq expected end.should eq(expected), file: file, line: line
end end
end end

View file

@ -286,3 +286,7 @@ end
def as_nodes(source) def as_nodes(source)
Ameba::TestNodeVisitor.new(as_node source) Ameba::TestNodeVisitor.new(as_node source)
end end
def trailing_whitespace
' '
end

View file

@ -1,15 +1,21 @@
module Ameba::Rule::Lint module Ameba::Rule::Lint
# This rule checks for mistyped shorthand assignments. # This rule checks for mistyped shorthand assignments.
# #
# # bad # This is considered invalid:
# x =- y
# x =+ y
# x =! y
# #
# # good # ```
# x = -y
# x = +y
# x = !y
# ```
#
# And this is valid:
#
# ```
# x -= y # or x = -y # x -= y # or x = -y
# x += y # or x = +y # x += y # or x = +y
# x != y # or x = !y # x != y # or x = !y
# ```
# #
# YAML configuration example: # YAML configuration example:
# #
@ -27,9 +33,9 @@ module Ameba::Rule::Lint
MSG = "Suspicious assignment detected. Did you mean `%s`?" MSG = "Suspicious assignment detected. Did you mean `%s`?"
MISTAKES = { MISTAKES = {
"=-" => "-=", "=-": "-=",
"=+" => "+=", "=+": "+=",
"=!" => "!=", "=!": "!=",
} }
def test(source, node : Crystal::Assign) def test(source, node : Crystal::Assign)
@ -43,9 +49,9 @@ module Ameba::Rule::Lint
op_text = source_between(op_location, op_end_location, source.lines) op_text = source_between(op_location, op_end_location, source.lines)
return unless op_text return unless op_text
return unless MISTAKES.has_key?(op_text) return unless suggestion = MISTAKES[op_text]?
issue_for op_location, op_end_location, MSG % MISTAKES[op_text] issue_for op_location, op_end_location, MSG % suggestion
end end
end end
end end

View file

@ -34,19 +34,11 @@ module Ameba::Rule::Lint
description "Disallows empty expressions" description "Disallows empty expressions"
end end
MSG = "Avoid empty expression %s" MSG = "Avoid empty expressions"
MSG_EXRS = "Avoid empty expressions"
def test(source, node : Crystal::NilLiteral)
exp = node_source(node, source.lines)
return if exp.in?(nil, "nil")
issue_for node, MSG % exp
end
def test(source, node : Crystal::Expressions) def test(source, node : Crystal::Expressions)
return unless node.expressions.size == 1 && node.expressions.first.nop? return unless node.expressions.size == 1 && node.expressions.first.nop?
issue_for node, MSG_EXRS issue_for node, MSG
end end
end end
end end

View file

@ -63,7 +63,18 @@ module Ameba::Rule::Lint
next if scope.references?(argument.variable) next if scope.references?(argument.variable)
name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : "_#{argument.name}" name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : "_#{argument.name}"
issue_for argument.node, MSG % {argument.name, name_suggestion} message = MSG % {argument.name, name_suggestion}
location = argument.node.location
end_location = location.try &.adjust(column_number: argument.name.size - 1)
if location && end_location
issue_for argument.node, message do |corrector|
corrector.replace(location, end_location, name_suggestion)
end
else
issue_for argument.node, message
end
end end
end end
end end

View file

@ -31,6 +31,8 @@ module Ameba::Rule::Lint
# Enabled: true # Enabled: true
# ``` # ```
class UnusedBlockArgument < Base class UnusedBlockArgument < Base
include AST::Util
properties do properties do
description "Disallows unused block arguments" description "Disallows unused block arguments"
end end
@ -51,12 +53,27 @@ module Ameba::Rule::Lint
return if block_arg.anonymous? return if block_arg.anonymous?
return if scope.references?(block_arg.variable) return if scope.references?(block_arg.variable)
location = block_arg.node.location
end_location = location.try &.adjust(column_number: block_arg.name.size - 1)
if scope.yields? if scope.yields?
if location && end_location
issue_for location, end_location, MSG_YIELDED do |corrector|
corrector.remove(location, end_location)
end
else
issue_for block_arg.node, MSG_YIELDED issue_for block_arg.node, MSG_YIELDED
end
else else
return if block_arg.ignored? return if block_arg.ignored?
if location && end_location
issue_for location, end_location, MSG_UNUSED % block_arg.name do |corrector|
corrector.insert_before(location, '_')
end
else
issue_for block_arg.node, MSG_UNUSED % block_arg.name issue_for block_arg.node, MSG_UNUSED % block_arg.name
end end
end end
end end
end
end end

View file

@ -42,14 +42,10 @@ module Ameba::Rule::Performance
return unless node.block.nil? && node.args.empty? return unless node.block.nil? && node.args.empty?
return unless node.obj return unless node.obj
return unless location = node.location
return unless name_location = node.name_location return unless name_location = node.name_location
return unless end_location = name_end_location(node) return unless end_location = name_end_location(node)
issue_for name_location, end_location, MSG do |corrector| issue_for name_location, end_location, MSG
corrector.insert_before(location, '!')
corrector.replace(name_location, end_location, "empty?")
end
end end
end end
end end

View file

@ -57,8 +57,7 @@ module Ameba::Rule::Style
if cond.is_a?(Crystal::Assign) && allow_safe_assignment? if cond.is_a?(Crystal::Assign) && allow_safe_assignment?
issue_for cond, MSG_MISSING do |corrector| issue_for cond, MSG_MISSING do |corrector|
corrector.insert_before(cond, '(') corrector.wrap(cond, '(', ')')
corrector.insert_after(cond, ')')
end end
return return
end end

View file

@ -50,7 +50,36 @@ module Ameba::Rule::Style
MSG = "Favour if over unless with else" MSG = "Favour if over unless with else"
def test(source, node : Crystal::Unless) def test(source, node : Crystal::Unless)
issue_for node, MSG unless node.else.nop? return if node.else.nop?
location = node.location
cond_end_location = node.cond.end_location
else_location = node.else_location
end_location = node.end_location
unless location && cond_end_location && else_location && end_location
issue_for node, MSG
return
end
issue_for location, cond_end_location, MSG do |corrector|
keyword_begin_pos = source.pos(location)
keyword_end_pos = keyword_begin_pos + {{ "unless".size }}
keyword_range = keyword_begin_pos...keyword_end_pos
cond_end_pos = source.pos(cond_end_location, end: true)
else_begin_pos = source.pos(else_location)
body_range = cond_end_pos...else_begin_pos
else_end_pos = else_begin_pos + {{ "else".size }}
end_end_pos = source.pos(end_location, end: true)
end_begin_pos = end_end_pos - {{ "end".size }}
else_range = else_end_pos...end_begin_pos
corrector.replace(keyword_range, "if")
corrector.replace(body_range, source.code[else_range])
corrector.replace(else_range, source.code[body_range])
end
end end
end end
end end

View file

@ -130,7 +130,7 @@ module Ameba
rule.test(source) rule.test(source)
end end
check_unneeded_directives(source) check_unneeded_directives(source)
break unless autocorrect? && source.correct break unless autocorrect? && source.correct?
# The issues that couldn't be corrected will be found again so we # The issues that couldn't be corrected will be found again so we
# only keep the corrected ones in order to avoid duplicate reporting. # only keep the corrected ones in order to avoid duplicate reporting.

View file

@ -24,9 +24,10 @@ module Ameba
# Corrects any correctable issues and updates `code`. # Corrects any correctable issues and updates `code`.
# Returns `false` if no issues were corrected. # Returns `false` if no issues were corrected.
def correct def correct?
corrector = Corrector.new(code) corrector = Corrector.new(code)
issues.each(&.correct(corrector)) issues.each(&.correct(corrector))
corrected_code = corrector.process corrected_code = corrector.process
return false if code == corrected_code return false if code == corrected_code
@ -75,5 +76,13 @@ module Ameba
def matches_path?(filepath) def matches_path?(filepath)
path.in?(filepath, File.expand_path(filepath)) path.in?(filepath, File.expand_path(filepath))
end end
# Converts an AST location to a string position.
def pos(location : Crystal::Location, end end_pos = false) : Int32
line, column = location.line_number, location.column_number
pos = lines[0...line - 1].sum(&.size) + line + column - 2
pos += 1 if end_pos
pos
end
end end
end end

View file

@ -18,41 +18,92 @@ class Ameba::Source
@rewriter.replace(loc_to_pos(location), loc_to_pos(end_location) + 1, content) @rewriter.replace(loc_to_pos(location), loc_to_pos(end_location) + 1, content)
end end
# :ditto:
def replace(range : Range(Int32, Int32), content)
begin_pos, end_pos = range.begin, range.end
end_pos -= 1 unless range.excludes_end?
@rewriter.replace(begin_pos, end_pos, content)
end
# Inserts the given strings before and after the given range. # Inserts the given strings before and after the given range.
def wrap(location, end_location, insert_before, insert_after) def wrap(location, end_location, insert_before, insert_after)
@rewriter.wrap(loc_to_pos(location), loc_to_pos(end_location) + 1, insert_before, insert_after) @rewriter.wrap(loc_to_pos(location), loc_to_pos(end_location) + 1, insert_before, insert_after)
end end
# :ditto:
def wrap(range : Range(Int32, Int32), insert_before, insert_after)
begin_pos, end_pos = range.begin, range.end
end_pos -= 1 unless range.excludes_end?
@rewriter.wrap(begin_pos, end_pos, insert_before, insert_after)
end
# Shortcut for `replace(location, end_location, "")` # Shortcut for `replace(location, end_location, "")`
def remove(location, end_location) def remove(location, end_location)
@rewriter.remove(loc_to_pos(location), loc_to_pos(end_location) + 1) @rewriter.remove(loc_to_pos(location), loc_to_pos(end_location) + 1)
end end
# Shortcut for `replace(range, "")`
def remove(range : Range(Int32, Int32))
begin_pos, end_pos = range.begin, range.end
end_pos -= 1 unless range.excludes_end?
@rewriter.remove(begin_pos, end_pos)
end
# Shortcut for `wrap(location, end_location, content, nil)` # Shortcut for `wrap(location, end_location, content, nil)`
def insert_before(location, end_location, content) def insert_before(location, end_location, content)
@rewriter.insert_before(loc_to_pos(location), loc_to_pos(end_location) + 1, content) @rewriter.insert_before(loc_to_pos(location), loc_to_pos(end_location) + 1, content)
end end
# Shortcut for `wrap(range, content, nil)`
def insert_before(range : Range(Int32, Int32), content)
begin_pos, end_pos = range.begin, range.end
end_pos -= 1 unless range.excludes_end?
@rewriter.insert_before(begin_pos, end_pos, content)
end
# Shortcut for `wrap(location, end_location, nil, content)` # Shortcut for `wrap(location, end_location, nil, content)`
def insert_after(location, end_location, content) def insert_after(location, end_location, content)
@rewriter.insert_after(loc_to_pos(location), loc_to_pos(end_location) + 1, content) @rewriter.insert_after(loc_to_pos(location), loc_to_pos(end_location) + 1, content)
end end
# Shortcut for `wrap(range, nil, content)`
def insert_after(range : Range(Int32, Int32), content)
begin_pos, end_pos = range.begin, range.end
end_pos -= 1 unless range.excludes_end?
@rewriter.insert_after(begin_pos, end_pos, content)
end
# Shortcut for `insert_before(location, location, content)` # Shortcut for `insert_before(location, location, content)`
def insert_before(location, content) def insert_before(location, content)
@rewriter.insert_before(loc_to_pos(location), content) @rewriter.insert_before(loc_to_pos(location), content)
end end
# Shortcut for `insert_before(pos.., content)`
def insert_before(pos : Int32, content)
@rewriter.insert_before(pos, content)
end
# Shortcut for `insert_after(location, location, content)` # Shortcut for `insert_after(location, location, content)`
def insert_after(location, content) def insert_after(location, content)
@rewriter.insert_after(loc_to_pos(location) + 1, content) @rewriter.insert_after(loc_to_pos(location) + 1, content)
end end
# Shortcut for `insert_after(...pos, content)`
def insert_after(pos : Int32, content)
@rewriter.insert_after(pos, content)
end
# Removes *size* characters prior to the source range. # Removes *size* characters prior to the source range.
def remove_preceding(location, end_location, size) def remove_preceding(location, end_location, size)
@rewriter.remove(loc_to_pos(location) - size, loc_to_pos(location)) @rewriter.remove(loc_to_pos(location) - size, loc_to_pos(location))
end end
# :ditto:
def remove_preceding(range : Range(Int32, Int32), size)
begin_pos = range.begin
@rewriter.remove(begin_pos - size, begin_pos)
end
# Removes *size* characters from the beginning of the given range. # Removes *size* characters from the beginning of the given range.
# If *size* is greater than the size of the range, the removed region can # If *size* is greater than the size of the range, the removed region can
# overrun the end of the range. # overrun the end of the range.
@ -60,6 +111,12 @@ class Ameba::Source
@rewriter.remove(loc_to_pos(location), loc_to_pos(location) + size) @rewriter.remove(loc_to_pos(location), loc_to_pos(location) + size)
end end
# :ditto:
def remove_leading(range : Range(Int32, Int32), size)
begin_pos = range.begin
@rewriter.remove(begin_pos, begin_pos + size)
end
# Removes *size* characters from the end of the given range. # Removes *size* characters from the end of the given range.
# If *size* is greater than the size of the range, the removed region can # If *size* is greater than the size of the range, the removed region can
# overrun the beginning of the range. # overrun the beginning of the range.
@ -67,6 +124,13 @@ class Ameba::Source
@rewriter.remove(loc_to_pos(end_location) + 1 - size, loc_to_pos(end_location) + 1) @rewriter.remove(loc_to_pos(end_location) + 1 - size, loc_to_pos(end_location) + 1)
end end
# :ditto:
def remove_trailing(range : Range(Int32, Int32), size)
end_pos = range.end
end_pos -= 1 unless range.excludes_end?
@rewriter.remove(end_pos - size, end_pos)
end
private def loc_to_pos(location : Crystal::Location | {Int32, Int32}) private def loc_to_pos(location : Crystal::Location | {Int32, Int32})
if location.is_a?(Crystal::Location) if location.is_a?(Crystal::Location)
line, column = location.line_number, location.column_number line, column = location.line_number, location.column_number

View file

@ -125,7 +125,9 @@ class Ameba::Source
private def check_range_validity(begin_pos, end_pos) private def check_range_validity(begin_pos, end_pos)
return unless begin_pos < 0 || end_pos > code.size return unless begin_pos < 0 || end_pos > code.size
raise IndexError.new("The range #{begin_pos}...#{end_pos} is outside the bounds of the source") raise IndexError.new(
"The range #{begin_pos}...#{end_pos} is outside the bounds of the source"
)
end end
end end
end end

View file

@ -2,6 +2,7 @@
class Ameba::Spec::AnnotatedSource class Ameba::Spec::AnnotatedSource
ANNOTATION_PATTERN_1 = /\A\s*(# )?(\^+|\^{})( error:)? / ANNOTATION_PATTERN_1 = /\A\s*(# )?(\^+|\^{})( error:)? /
ANNOTATION_PATTERN_2 = " # error: " ANNOTATION_PATTERN_2 = " # error: "
ABBREV = "[...]" ABBREV = "[...]"
getter lines : Array(String) getter lines : Array(String)
@ -15,6 +16,7 @@ class Ameba::Spec::AnnotatedSource
def self.parse(annotated_code) def self.parse(annotated_code)
lines = [] of String lines = [] of String
annotations = [] of {Int32, String, String} annotations = [] of {Int32, String, String}
code_lines = annotated_code.split('\n') # must preserve trailing newline code_lines = annotated_code.split('\n') # must preserve trailing newline
code_lines.each do |code_line| code_lines.each do |code_line|
case case
@ -39,7 +41,9 @@ class Ameba::Spec::AnnotatedSource
# NOTE: Annotations are sorted so that reconstructing the annotation # NOTE: Annotations are sorted so that reconstructing the annotation
# text via `#to_s` is deterministic. # text via `#to_s` is deterministic.
def initialize(@lines, annotations : Enumerable({Int32, String, String})) def initialize(@lines, annotations : Enumerable({Int32, String, String}))
@annotations = annotations.to_a.sort_by { |line, _, message| {line, message} } @annotations = annotations.to_a.sort_by do |line, _, message|
{line, message}
end
end end
# Annotates the source code with the Ameba issues provided. # Annotates the source code with the Ameba issues provided.
@ -47,7 +51,9 @@ class Ameba::Spec::AnnotatedSource
# NOTE: Annotations are sorted so that reconstructing the annotation # NOTE: Annotations are sorted so that reconstructing the annotation
# text via `#to_s` is deterministic. # text via `#to_s` is deterministic.
def initialize(@lines, issues : Enumerable(Issue)) def initialize(@lines, issues : Enumerable(Issue))
@annotations = issues_to_annotations(issues).sort_by { |line, _, message| {line, message} } @annotations = issues_to_annotations(issues).sort_by do |line, _, message|
{line, message}
end
end end
def ==(other) def ==(other)

View file

@ -13,8 +13,8 @@ module Ameba::Spec
def failure_message(source) def failure_message(source)
String.build do |str| String.build do |str|
str << "Source expected to be valid, but there are issues: \n\n" str << "Source expected to be valid, but there are issues: \n\n"
source.issues.reject(&.disabled?).each do |e| source.issues.reject(&.disabled?).each do |issue|
str << " * #{e.rule.name}: #{e.message}\n" str << " * #{issue.rule.name}: #{issue.message}\n"
end end
end end
end end

View file

@ -129,7 +129,7 @@ module Ameba::Spec::ExpectIssue
end end
def expect_correction(source, correction, *, file = __FILE__, line = __LINE__) def expect_correction(source, correction, *, file = __FILE__, line = __LINE__)
raise "Use `expect_no_corrections` if the code will not change" unless source.correct raise "Use `expect_no_corrections` if the code will not change" unless source.correct?
return if correction == source.code return if correction == source.code
fail <<-MSG, file, line fail <<-MSG, file, line
@ -144,7 +144,7 @@ module Ameba::Spec::ExpectIssue
end end
def expect_no_corrections(source, *, file = __FILE__, line = __LINE__) def expect_no_corrections(source, *, file = __FILE__, line = __LINE__)
return unless source.correct return unless source.correct?
fail <<-MSG, file, line fail <<-MSG, file, line
Expected no corrections, but got: Expected no corrections, but got:

View file

@ -14,9 +14,5 @@ module Ameba
end end
end end
def trailing_whitespace
' '
end
include Ameba::Spec::BeValid include Ameba::Spec::BeValid
include Ameba::Spec::ExpectIssue include Ameba::Spec::ExpectIssue