mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Merge branch 'master' into Sija/release-1.4.0
This commit is contained in:
commit
74407cc8f5
85 changed files with 848 additions and 1105 deletions
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal 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
74
.github/workflows/cd.yml
vendored
Normal 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 }}
|
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
|
@ -2,7 +2,9 @@ name: CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
schedule:
|
||||
- cron: "0 3 * * 1" # Every monday at 3 AM
|
||||
|
||||
|
@ -17,20 +19,21 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Install Crystal
|
||||
uses: oprypin/install-crystal@v1
|
||||
uses: crystal-lang/install-crystal@v1
|
||||
with:
|
||||
crystal: ${{ matrix.crystal }}
|
||||
|
||||
- name: Download source
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: shards install
|
||||
env:
|
||||
SHARDS_OPTS: --ignore-crystal-version
|
||||
|
||||
- name: Run specs
|
||||
run: make test
|
||||
run: crystal spec
|
||||
|
||||
- name: Check formatting
|
||||
run: crystal tool format --check
|
||||
- name: Build ameba binary
|
||||
run: shards build -Dpreview_mt
|
||||
|
||||
- name: Run ameba linter
|
||||
run: bin/ameba --all
|
||||
|
|
45
.github/workflows/docs.yml
vendored
45
.github/workflows/docs.yml
vendored
|
@ -4,51 +4,32 @@ on:
|
|||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-and-deploy:
|
||||
concurrency: ci-${{ github.ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Inject slug/short variables
|
||||
uses: rlespinasse/github-slug-action@v3.x
|
||||
uses: rlespinasse/github-slug-action@v4
|
||||
|
||||
- name: Install Crystal
|
||||
uses: oprypin/install-crystal@v1
|
||||
uses: crystal-lang/install-crystal@v1
|
||||
|
||||
- name: Download source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: shards install
|
||||
env:
|
||||
SHARDS_OPTS: --ignore-crystal-version
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
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
|
||||
run: crystal docs --project-version="${{ env.GITHUB_REF_SLUG }}" --source-refname="${{ env.GITHUB_SHA_SHORT }}"
|
||||
|
||||
- name: Deploy docs 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: docs
|
||||
CLEAN: true
|
||||
branch: gh-pages
|
||||
folder: docs
|
||||
clean: true
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
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
|
||||
WORKDIR /ameba
|
||||
COPY . /ameba/
|
||||
RUN make clean && make
|
||||
|
||||
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
|
||||
WORKDIR /src
|
||||
COPY --from=builder /ameba/bin/ameba /usr/bin/
|
||||
|
|
26
Makefile
26
Makefile
|
@ -4,19 +4,35 @@ PREFIX ?= /usr/local
|
|||
SHARD_BIN ?= ../../bin
|
||||
CRFLAGS ?= -Dpreview_mt
|
||||
|
||||
build: bin/ameba
|
||||
bin/ameba:
|
||||
.PHONY: build
|
||||
build:
|
||||
$(SHARDS_BIN) build $(CRFLAGS)
|
||||
|
||||
.PHONY: lint
|
||||
lint: build
|
||||
./bin/ameba --all
|
||||
|
||||
.PHONY: spec
|
||||
spec:
|
||||
$(CRYSTAL_BIN) spec
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f ./bin/ameba ./bin/ameba.dwarf
|
||||
|
||||
.PHONY: install
|
||||
install: build
|
||||
mkdir -p $(PREFIX)/bin
|
||||
cp ./bin/ameba $(PREFIX)/bin
|
||||
|
||||
.PHONY: bin
|
||||
bin: build
|
||||
mkdir -p $(SHARD_BIN)
|
||||
cp ./bin/ameba $(SHARD_BIN)
|
||||
|
||||
.PHONY: run_file
|
||||
run_file:
|
||||
cp -n ./bin/ameba.cr $(SHARD_BIN) || true
|
||||
test: build
|
||||
$(CRYSTAL_BIN) spec
|
||||
./bin/ameba --all
|
||||
|
||||
.PHONY: test
|
||||
test: spec lint
|
||||
|
|
|
@ -162,16 +162,16 @@ $ brew install ameba
|
|||
Build the image:
|
||||
|
||||
```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`:
|
||||
|
||||
```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
|
||||
|
||||
|
|
|
@ -10,29 +10,29 @@ module Ameba::AST
|
|||
describe ".of" do
|
||||
context "Crystal::If" do
|
||||
it "constructs a branch in If.cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method
|
||||
if a = get_something # --> Crystal::Assign
|
||||
puts a
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = get_something"
|
||||
end
|
||||
|
||||
it "constructs a branch in If.then" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method
|
||||
if true
|
||||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in If.else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method
|
||||
if true
|
||||
nil
|
||||
|
@ -40,45 +40,45 @@ module Ameba::AST
|
|||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in inline If" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
a = 0 if a == 2 # --> Crystal::Assign
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 0"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::Unless" do
|
||||
it "constructs a branch in Unless.cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method
|
||||
unless a = get_something # --> Crystal::Assign
|
||||
puts a
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = get_something"
|
||||
end
|
||||
|
||||
it "constructs a branch in Unless.then" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method
|
||||
unless true
|
||||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a new branch in Unless.else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method
|
||||
unless true
|
||||
nil
|
||||
|
@ -86,188 +86,188 @@ module Ameba::AST
|
|||
a = 2 # --> Crystal::Assign
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in inline Unless" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
(a = 0; b = 3) unless a == 2 # --> Crystal::Expressions
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "(a = 0\nb = 3)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::BinaryOp" 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)
|
||||
(a = 2) && do_something
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "(a = 2)"
|
||||
end
|
||||
|
||||
it "constructs a branch in right node" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
do_something || (a = 0)
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "(a = 0)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::Case" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
case (a = 2)
|
||||
when true then nil
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "(a = 2)"
|
||||
end
|
||||
|
||||
it "constructs a branch in when" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
case a
|
||||
when a = 3 then nil
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "when a = 3\n nil\n"
|
||||
end
|
||||
|
||||
it "constructs a branch in else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
case a
|
||||
when true then nil
|
||||
else a = 4
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 4"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::While" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
while a = 1
|
||||
nil
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in body" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
while true
|
||||
b = (a = 1)
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "b = (a = 1)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::Until" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
until a = 1
|
||||
nil
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in body" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
until false
|
||||
b = (a = 1)
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "b = (a = 1)"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::ExceptionHandler" do
|
||||
it "constructs a branch in body" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
a = 1
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in a rescue" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
rescue
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in else" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
rescue
|
||||
else
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
|
||||
it "constructs a branch in ensure" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
rescue
|
||||
ensure
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 1"
|
||||
end
|
||||
end
|
||||
|
||||
context "Crystal::MacroIf" do
|
||||
it "constructs a branch in cond" do
|
||||
branch = branch_of_assign_in_def %(
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
{% if a = 2 %}
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "a = 2"
|
||||
end
|
||||
|
||||
it "constructs a branch in then" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method(a)
|
||||
{% if true %}
|
||||
a = 2
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first)
|
||||
branch.to_s.strip.should eq "a = 2"
|
||||
end
|
||||
|
@ -275,24 +275,24 @@ module Ameba::AST
|
|||
|
||||
context "Crystal::MacroFor" do
|
||||
it "constructs a branch in body" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method(a)
|
||||
{% for x in [1, 2, 3] %}
|
||||
a = 2
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch = Branch.of(nodes.macro_literal_nodes.first, nodes.def_nodes.first)
|
||||
branch.to_s.strip.should eq "a = 2"
|
||||
end
|
||||
end
|
||||
|
||||
it "returns nil if branch does not exist" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branch = Branch.of(nodes.assign_nodes.first, nodes.def_nodes.first)
|
||||
branch.should be_nil
|
||||
end
|
||||
|
@ -300,11 +300,11 @@ module Ameba::AST
|
|||
|
||||
describe "#initialize" do
|
||||
it "creates new branch" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
if true
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.node.should_not be_nil
|
||||
|
@ -313,22 +313,22 @@ module Ameba::AST
|
|||
|
||||
describe "delegation" do
|
||||
it "delegates to_s to node" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
if true
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.to_s.should eq branch.node.to_s
|
||||
end
|
||||
|
||||
it "delegates locations to node" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
if true
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.location.should eq branch.node.location
|
||||
|
@ -338,22 +338,22 @@ module Ameba::AST
|
|||
|
||||
describe "#in_loop?" do
|
||||
it "returns true if branch is in a loop" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
while true
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.while_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.in_loop?.should be_true
|
||||
end
|
||||
|
||||
it "returns false if branch is not in a loop" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
if a > 2
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.if_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.in_loop?.should be_false
|
||||
|
|
|
@ -4,20 +4,20 @@ module Ameba::AST
|
|||
describe Branchable do
|
||||
describe "#initialize" 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
|
||||
end
|
||||
end
|
||||
|
||||
describe "delegation" 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.to_s.should eq node.to_s
|
||||
end
|
||||
|
||||
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.location.should eq node.location
|
||||
branchable.end_location.should eq node.end_location
|
||||
|
@ -26,22 +26,22 @@ module Ameba::AST
|
|||
|
||||
describe "#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
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
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.location.should eq node.location
|
||||
flow_expression.end_location.should eq node.end_location
|
||||
|
@ -27,20 +27,20 @@ module Ameba::AST
|
|||
|
||||
describe "#unreachable_nodes" do
|
||||
it "returns unreachable nodes" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def foobar
|
||||
return
|
||||
a = 1
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
node = nodes.expressions_nodes.first
|
||||
flow_expression = FlowExpression.new node, false
|
||||
flow_expression.unreachable_nodes.should eq nodes.assign_nodes
|
||||
end
|
||||
|
||||
it "returns nil if there is no unreachable node after loop" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def run
|
||||
idx = items.size - 1
|
||||
while 0 <= idx
|
||||
|
@ -49,19 +49,19 @@ module Ameba::AST
|
|||
|
||||
puts "foo"
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
node = nodes.expressions_nodes.first
|
||||
flow_expression = FlowExpression.new node, false
|
||||
flow_expression.unreachable_nodes.empty?.should eq true
|
||||
end
|
||||
|
||||
it "returns nil if there is no unreachable node" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def foobar
|
||||
a = 1
|
||||
return a
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
node = nodes.expressions_nodes.first
|
||||
flow_expression = FlowExpression.new node, false
|
||||
flow_expression.unreachable_nodes.empty?.should eq true
|
||||
|
|
|
@ -49,14 +49,14 @@ module Ameba::AST
|
|||
|
||||
describe "#references?" do
|
||||
it "returns true if current scope references variable" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method
|
||||
a = 2
|
||||
block do
|
||||
3.times { |i| a = a + i }
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
@ -69,14 +69,14 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
it "returns false if inner scopes are not checked" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method
|
||||
a = 2
|
||||
block do
|
||||
3.times { |i| a = a + i }
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
@ -89,7 +89,7 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
it "returns false if current scope does not reference variable" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method
|
||||
a = 2
|
||||
block do
|
||||
|
@ -97,7 +97,7 @@ module Ameba::AST
|
|||
3.times { |i| b = b + i }
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
@ -150,57 +150,53 @@ module Ameba::AST
|
|||
|
||||
describe "#block?" do
|
||||
it "returns true if Crystal::Block" do
|
||||
nodes = as_nodes %(
|
||||
3.times {}
|
||||
)
|
||||
nodes = as_nodes("3.times {}")
|
||||
scope = Scope.new nodes.block_nodes.first
|
||||
scope.block?.should be_true
|
||||
end
|
||||
|
||||
it "returns false otherwise" do
|
||||
scope = Scope.new as_node "a = 1"
|
||||
scope = Scope.new as_node("a = 1")
|
||||
scope.block?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#spawn_block?" do
|
||||
it "returns true if a node is a spawn block" do
|
||||
nodes = as_nodes %(
|
||||
spawn {}
|
||||
)
|
||||
nodes = as_nodes("spawn {}")
|
||||
scope = Scope.new nodes.block_nodes.first
|
||||
scope.spawn_block?.should be_true
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
describe "#in_macro?" do
|
||||
it "returns true if Crystal::Macro" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
macro included
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.macro_nodes.first
|
||||
scope.in_macro?.should be_true
|
||||
end
|
||||
|
||||
it "returns true if node is nested to Crystal::Macro" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
macro included
|
||||
{{ @type.each do |type| a = type end }}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
outer_scope = Scope.new nodes.macro_nodes.first
|
||||
scope = Scope.new nodes.block_nodes.first, outer_scope
|
||||
scope.in_macro?.should be_true
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,15 +41,14 @@ module Ameba::AST
|
|||
|
||||
describe "#branch" do
|
||||
it "returns the branch of the assignment" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method(a)
|
||||
if a
|
||||
a = 3 # --> Crystal::Expressions
|
||||
puts a
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
|
@ -58,7 +57,7 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
it "returns inner branch" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method(a, b)
|
||||
if a
|
||||
if b
|
||||
|
@ -66,7 +65,7 @@ module Ameba::AST
|
|||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
|
@ -75,12 +74,11 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
it "returns nil if assignment does not have a branch" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method(a)
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
|
@ -90,12 +88,11 @@ module Ameba::AST
|
|||
|
||||
describe "#transformed?" do
|
||||
it "returns false if the assignment is not transformed by the compiler" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method(a)
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
|
@ -103,11 +100,10 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
it "returns true if the assignment is transformed by the compiler" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
array.each do |(a, b)|
|
||||
end
|
||||
)
|
||||
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.block_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
|
|
|
@ -79,12 +79,12 @@ module Ameba::AST
|
|||
|
||||
describe "#captured_by_block?" do
|
||||
it "returns truthy if the variable is captured by block" do
|
||||
nodes = as_nodes %(
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
def method
|
||||
a = 2
|
||||
3.times { |i| a = a + i }
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
@ -95,12 +95,12 @@ module Ameba::AST
|
|||
variable.captured_by_block?.should be_truthy
|
||||
end
|
||||
|
||||
it "returns falsey if the variable is not captured by the block" do
|
||||
scope = Scope.new as_node %(
|
||||
it "returns falsy if the variable is not captured by the block" do
|
||||
scope = Scope.new as_node <<-CRYSTAL
|
||||
def method
|
||||
a = 1
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
scope.add_variable Crystal::Var.new "a"
|
||||
variable = scope.variables.first
|
||||
variable.captured_by_block?.should be_falsey
|
||||
|
|
|
@ -19,33 +19,33 @@ module Ameba::AST
|
|||
end
|
||||
|
||||
it "is 1 if there is Macro::For" do
|
||||
code = %(
|
||||
def initialize()
|
||||
code = <<-CRYSTAL
|
||||
def initialize
|
||||
{% for c in ALL_NODES %}
|
||||
true || false
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
node = Crystal::Parser.new(code).parse
|
||||
visitor = CountingVisitor.new node
|
||||
visitor.count.should eq 1
|
||||
end
|
||||
|
||||
it "is 1 if there is Macro::If" do
|
||||
code = %(
|
||||
def initialize()
|
||||
code = <<-CRYSTAL
|
||||
def initialize
|
||||
{% if foo.bar? %}
|
||||
true || false
|
||||
{% end %}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
node = Crystal::Parser.new(code).parse
|
||||
visitor = CountingVisitor.new node
|
||||
visitor.count.should eq 1
|
||||
end
|
||||
|
||||
it "increases count for every exhaustive case" do
|
||||
code = %(
|
||||
code = <<-CRYSTAL
|
||||
def hello(a : Int32 | Int64 | Float32 | Float64)
|
||||
case a
|
||||
in Int32 then "int32"
|
||||
|
@ -54,7 +54,7 @@ module Ameba::AST
|
|||
in Float64 then "float64"
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
node = Crystal::Parser.new(code).parse
|
||||
visitor = CountingVisitor.new node
|
||||
visitor.count.should eq 2
|
||||
|
|
|
@ -6,17 +6,17 @@ module Ameba::AST
|
|||
describe FlowExpressionVisitor do
|
||||
it "creates an expression for return" do
|
||||
rule = FlowExpressionRule.new
|
||||
FlowExpressionVisitor.new rule, Source.new %(
|
||||
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
|
||||
def foo
|
||||
return :bar
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.expressions.size.should eq 1
|
||||
end
|
||||
|
||||
it "can create multiple expressions" do
|
||||
rule = FlowExpressionRule.new
|
||||
FlowExpressionVisitor.new rule, Source.new %(
|
||||
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
|
||||
def foo
|
||||
if bar
|
||||
return :baz
|
||||
|
@ -24,42 +24,42 @@ module Ameba::AST
|
|||
return :foobar
|
||||
end
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.expressions.size.should eq 3
|
||||
end
|
||||
|
||||
it "properly creates nested flow expressions" do
|
||||
rule = FlowExpressionRule.new
|
||||
FlowExpressionVisitor.new rule, Source.new %(
|
||||
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
|
||||
def foo
|
||||
return(
|
||||
return (
|
||||
loop do
|
||||
break if a > 1
|
||||
return a
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.expressions.size.should eq 4
|
||||
end
|
||||
|
||||
it "creates an expression for break" do
|
||||
rule = FlowExpressionRule.new
|
||||
FlowExpressionVisitor.new rule, Source.new %(
|
||||
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
|
||||
while true
|
||||
break
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.expressions.size.should eq 1
|
||||
end
|
||||
|
||||
it "creates an expression for next" do
|
||||
rule = FlowExpressionRule.new
|
||||
FlowExpressionVisitor.new rule, Source.new %(
|
||||
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
|
||||
while true
|
||||
next if something
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.expressions.size.should eq 1
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,11 +5,11 @@ module Ameba::AST
|
|||
rule = RedundantControlExpressionRule.new
|
||||
|
||||
describe RedundantControlExpressionVisitor do
|
||||
node = as_node %(
|
||||
node = as_node <<-CRYSTAL
|
||||
a = 1
|
||||
b = 2
|
||||
return a + b
|
||||
)
|
||||
CRYSTAL
|
||||
subject = RedundantControlExpressionVisitor.new(rule, source, node)
|
||||
|
||||
it "assigns valid attributes" do
|
||||
|
|
|
@ -4,37 +4,37 @@ module Ameba::AST
|
|||
describe ScopeVisitor do
|
||||
it "creates a scope for the def" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
def method
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 1
|
||||
end
|
||||
|
||||
it "creates a scope for the proc" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
-> {}
|
||||
)
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 1
|
||||
end
|
||||
|
||||
it "creates a scope for the block" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
3.times {}
|
||||
)
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 2
|
||||
end
|
||||
|
||||
context "inner scopes" do
|
||||
it "creates scope for block inside def" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
def method
|
||||
3.times {}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 2
|
||||
rule.scopes.last.outer_scope.should_not be_nil
|
||||
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
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new %(
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
3.times do
|
||||
2.times {}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 3
|
||||
inner_block = rule.scopes.first
|
||||
outer_block = rule.scopes.last
|
||||
|
|
|
@ -4,10 +4,12 @@ module Ameba::AST
|
|||
describe TopLevelNodesVisitor do
|
||||
describe "#require_nodes" do
|
||||
it "returns require node" do
|
||||
source = Source.new %(
|
||||
source = Source.new <<-CRYSTAL
|
||||
require "foo"
|
||||
def bar; end
|
||||
)
|
||||
|
||||
def bar
|
||||
end
|
||||
CRYSTAL
|
||||
visitor = TopLevelNodesVisitor.new(source.ast)
|
||||
visitor.require_nodes.size.should eq 1
|
||||
visitor.require_nodes.first.to_s.should eq %q(require "foo")
|
||||
|
|
|
@ -26,135 +26,135 @@ module Ameba
|
|||
end
|
||||
|
||||
it "disables a rule with a comment directive" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
# ameba:disable #{NamedRule.name}
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {1, 12}, message: "Error!")
|
||||
s.should be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {1, 12}, message: "Error!")
|
||||
source.should be_valid
|
||||
end
|
||||
|
||||
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}
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {1, 12}, message: "Error!")
|
||||
s.should be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {1, 12}, message: "Error!")
|
||||
source.should be_valid
|
||||
end
|
||||
|
||||
it "does not disable a rule of a different name" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
# ameba:disable WrongName
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {2, 12}, message: "Error!")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {2, 12}, message: "Error!")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
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
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
s.should be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
source.should be_valid
|
||||
end
|
||||
|
||||
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
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
s.should be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
source.should be_valid
|
||||
end
|
||||
|
||||
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
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
it "does not disable if comment directive has wrong place" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
# ameba:disable #{NamedRule.name}
|
||||
#
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
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}
|
||||
Time.epoch(1483859302)
|
||||
end
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
it "does not disable if that is not a comment directive" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
"ameba:disable #{NamedRule.name}"
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
it "does not disable if that is a commented out directive" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
# # ameba:disable #{NamedRule.name}
|
||||
Time.epoch(1483859302)
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {3, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
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}
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
context "with group name" do
|
||||
it "disables one rule with a group" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
a = 1 # ameba:disable #{DummyRule.rule_name}
|
||||
)
|
||||
s.add_issue(DummyRule.new, location: {1, 12}, message: "")
|
||||
s.should be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(DummyRule.new, location: {1, 12}, message: "")
|
||||
source.should be_valid
|
||||
end
|
||||
|
||||
it "doesn't disable others rules" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
a = 1 # ameba:disable #{DummyRule.rule_name}
|
||||
)
|
||||
s.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(NamedRule.new, location: {2, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
|
||||
it "disables a hole group of rules" do
|
||||
s = Source.new %Q(
|
||||
source = Source.new <<-CRYSTAL
|
||||
a = 1 # ameba:disable #{DummyRule.group_name}
|
||||
)
|
||||
s.add_issue(DummyRule.new, location: {1, 12}, message: "")
|
||||
s.should be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(DummyRule.new, location: {1, 12}, message: "")
|
||||
source.should be_valid
|
||||
end
|
||||
|
||||
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
|
||||
)
|
||||
s.add_issue(DummyRule.new, location: {2, 12}, message: "")
|
||||
s.should_not be_valid
|
||||
CRYSTAL
|
||||
source.add_issue(DummyRule.new, location: {2, 12}, message: "")
|
||||
source.should_not be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ module Ameba::Rule::Layout
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure max length of the line" do
|
||||
it "#max_length" do
|
||||
rule = LineLength.new
|
||||
rule.max_length = long_line.size
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module Ameba::Rule::Layout
|
|||
|
||||
describe TrailingWhitespace 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
|
||||
|
||||
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"
|
||||
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
|
||||
|
|
|
@ -94,16 +94,6 @@ module Ameba::Rule::Lint
|
|||
a
|
||||
CRYSTAL
|
||||
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
|
||||
|
||||
context "boolean on the left" do
|
||||
|
@ -165,17 +155,6 @@ module Ameba::Rule::Lint
|
|||
a
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -28,16 +28,5 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -31,16 +31,5 @@ module Ameba::Rule::Lint
|
|||
|
||||
expect_no_corrections source
|
||||
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
|
||||
|
|
|
@ -17,33 +17,10 @@ module Ameba::Rule::Lint
|
|||
require "big"
|
||||
require "math"
|
||||
require "big"
|
||||
# ^{} error: Duplicated require of `big`
|
||||
# ^^^^^^^^^^^ error: Duplicated require of `big`
|
||||
CRYSTAL
|
||||
|
||||
expect_no_corrections source
|
||||
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
|
||||
|
|
|
@ -3,17 +3,17 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
subject = Rule::Lint::EmptyExpression.new
|
||||
|
||||
def it_detects_empty_expression(code)
|
||||
it "detects empty expression" do
|
||||
private def it_detects_empty_expression(code, *, file = __FILE__, line = __LINE__)
|
||||
it %(detects empty expression "#{code}"), file, line do
|
||||
s = Source.new code
|
||||
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
|
||||
|
||||
describe Rule::Lint::EmptyExpression do
|
||||
it "passes if there is no empty expression" do
|
||||
s = Source.new %(
|
||||
s = Source.new <<-CRYSTAL
|
||||
def method()
|
||||
end
|
||||
|
||||
|
@ -30,7 +30,7 @@ module Ameba
|
|||
|
||||
begin "" end
|
||||
[nil] << nil
|
||||
)
|
||||
CRYSTAL
|
||||
subject.catch(s).should be_valid
|
||||
end
|
||||
|
||||
|
@ -84,11 +84,6 @@ module Ameba
|
|||
it_detects_empty_expression %(
|
||||
begin; end
|
||||
)
|
||||
it_detects_empty_expression %(
|
||||
begin
|
||||
nil
|
||||
end
|
||||
)
|
||||
it_detects_empty_expression %(
|
||||
begin
|
||||
()
|
||||
|
@ -110,18 +105,5 @@ module Ameba
|
|||
)
|
||||
subject.catch(s).should be_valid
|
||||
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
|
||||
|
|
|
@ -64,21 +64,5 @@ module Ameba::Rule::Lint
|
|||
end
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -32,17 +32,5 @@ module Ameba::Rule::Lint
|
|||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicated keys in hash literal: "key1", "key2"
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -58,18 +58,5 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -58,18 +58,5 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -32,18 +32,5 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -68,14 +68,14 @@ module Ameba::Rule::Lint
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure string_array_unwanted_symbols" do
|
||||
it "#string_array_unwanted_symbols" do
|
||||
rule = PercentArrays.new
|
||||
rule.string_array_unwanted_symbols = ","
|
||||
s = Source.new %( %w("one") )
|
||||
rule.catch(s).should be_valid
|
||||
end
|
||||
|
||||
it "allows to configure symbol_array_unwanted_symbols" do
|
||||
it "#symbol_array_unwanted_symbols" do
|
||||
rule = PercentArrays.new
|
||||
rule.symbol_array_unwanted_symbols = ","
|
||||
s = Source.new %( %i(:one) )
|
||||
|
|
|
@ -25,16 +25,5 @@ module Ameba::Rule::Lint
|
|||
# ^^^^^ error: rand(1) always returns 0
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -77,7 +77,7 @@ module Ameba::Rule::Lint
|
|||
issue = s.issues.first
|
||||
issue.rule.should_not be_nil
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
@ -155,7 +155,7 @@ module Ameba::Rule::Lint
|
|||
issue = s.issues.first
|
||||
issue.rule.should_not be_nil
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,7 +76,7 @@ module Ameba::Rule::Lint
|
|||
issue = s.issues.first
|
||||
issue.rule.should_not be_nil
|
||||
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`"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -157,20 +157,6 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
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
|
||||
it "does not report shadowed vars in outer scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
|
|
|
@ -194,24 +194,5 @@ module Ameba::Rule::Lint
|
|||
end
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -23,16 +23,6 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
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
|
||||
subject.severity.should eq Severity::Error
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ module Ameba::Rule::Lint
|
|||
|
||||
describe UnusedArgument do
|
||||
it "doesn't report if arguments are used" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c)
|
||||
a + b + c
|
||||
end
|
||||
|
@ -16,155 +16,158 @@ module Ameba::Rule::Lint
|
|||
end
|
||||
|
||||
->(i : Int32) { i + 1 }
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if method argument is unused" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
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
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
s.issues.first.message.should eq "Unused argument `c`. If it's necessary, use `_c` " \
|
||||
"as an argument name to indicate that it won't be used."
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
def method(a, b, _c)
|
||||
a + b
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if block argument is unused" do
|
||||
s = Source.new %(
|
||||
[1,2].each_with_index do |a, i|
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
[1, 2].each_with_index do |a, i|
|
||||
# ^ error: Unused argument `i`. [...]
|
||||
a
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
s.issues.first.message.should eq "Unused argument `i`. If it's necessary, use `_` " \
|
||||
"as an argument name to indicate that it won't be used."
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
[1, 2].each_with_index do |a, _|
|
||||
a
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if proc argument is unused" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
-> (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
|
||||
end
|
||||
)
|
||||
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."
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
-> (a : Int32, _b : String) do
|
||||
a = a + 1
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports multiple unused args" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
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
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
s.issues[0].message.should eq "Unused argument `a`. If it's necessary, use `_a` " \
|
||||
"as an argument name to indicate that it won't be used."
|
||||
s.issues[1].message.should eq "Unused argument `b`. If it's necessary, use `_b` " \
|
||||
"as an argument name to indicate that it won't be used."
|
||||
s.issues[2].message.should eq "Unused argument `c`. If it's necessary, use `_c` " \
|
||||
"as an argument name to indicate that it won't be used."
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
def method(_a, _b, _c)
|
||||
nil
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it is an instance var argument" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class A
|
||||
def method(@name)
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if a typed argument is used" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(x : Int32)
|
||||
3.times do
|
||||
puts x
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
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)
|
||||
puts x
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if argument starts with a _" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(_x)
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it is a block and used" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&block)
|
||||
block.call
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if block arg is not used" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&block)
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if unused and there is yield" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&block)
|
||||
yield 1
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it's an anonymous block" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&)
|
||||
yield 1
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if variable is referenced implicitly" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
def method(a, b)
|
||||
super
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if arg if referenced in case" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def foo(a)
|
||||
case a
|
||||
when /foo/
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if enum in a record" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Class
|
||||
record Record do
|
||||
enum Enum
|
||||
|
@ -172,59 +175,49 @@ module Ameba::Rule::Lint
|
|||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "super" do
|
||||
it "reports if variable is not referenced implicitly by super" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
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
|
||||
end
|
||||
end
|
||||
)
|
||||
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
|
||||
CRYSTAL
|
||||
|
||||
it "reports rule, location and message" do
|
||||
s = Source.new %(
|
||||
def method(a)
|
||||
expect_correction source, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
def method(a, _b)
|
||||
super a
|
||||
end
|
||||
end
|
||||
), "source.cr"
|
||||
subject.catch(s).should_not be_valid
|
||||
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"
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "macro" 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)
|
||||
{% arg %}
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
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)
|
||||
{% block %}
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report used macro args with equal names in record" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
record X do
|
||||
macro foo(a, b)
|
||||
{{ a }} + {{ b }}
|
||||
|
@ -234,12 +227,11 @@ module Ameba::Rule::Lint
|
|||
{{ a }} + {{ b }} + {{ c }}
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
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
|
||||
f.size.times do |i|
|
||||
{% if U == Float64 %}
|
||||
|
@ -249,65 +241,73 @@ module Ameba::Rule::Lint
|
|||
{% end %}
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
describe "#ignore_defs" do
|
||||
it "lets the rule to ignore def scopes if true" do
|
||||
subject.ignore_defs = true
|
||||
s = Source.new %(
|
||||
rule = UnusedArgument.new
|
||||
rule.ignore_defs = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
def method(a)
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "lets the rule not to ignore def scopes if false" do
|
||||
subject.ignore_defs = false
|
||||
s = Source.new %(
|
||||
rule = UnusedArgument.new
|
||||
rule.ignore_defs = false
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
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
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "#ignore_blocks" do
|
||||
it "lets the rule to ignore block scopes if true" do
|
||||
subject.ignore_blocks = true
|
||||
s = Source.new %(
|
||||
rule = UnusedArgument.new
|
||||
rule.ignore_blocks = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
3.times { |i| puts "yo!" }
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "lets the rule not to ignore block scopes if false" do
|
||||
subject.ignore_blocks = false
|
||||
s = Source.new %(
|
||||
rule = UnusedArgument.new
|
||||
rule.ignore_blocks = false
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
3.times { |i| puts "yo!" }
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
# ^ error: Unused argument `i`. If it's necessary, use `_` as an argument name to indicate that it won't be used.
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "#ignore_procs" do
|
||||
it "lets the rule to ignore proc scopes if true" do
|
||||
subject.ignore_procs = true
|
||||
s = Source.new %(
|
||||
rule = UnusedArgument.new
|
||||
rule.ignore_procs = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
->(a : Int32) {}
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "lets the rule not to ignore proc scopes if false" do
|
||||
subject.ignore_procs = false
|
||||
s = Source.new %(
|
||||
rule = UnusedArgument.new
|
||||
rule.ignore_procs = false
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
->(a : Int32) {}
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
# ^ error: Unused argument `a`. If it's necessary, use `_a` as an argument name to indicate that it won't be used.
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,105 +5,118 @@ module Ameba::Rule::Lint
|
|||
|
||||
describe UnusedBlockArgument do
|
||||
it "doesn't report if it is an instance var argument" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class A
|
||||
def initialize(&@callback)
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if anonymous" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c, &)
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
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)
|
||||
block.call
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if block arg is not used" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
def method(a, b, c, &block)
|
||||
# ^^^^^ error: Unused block argument `block`. [...]
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
def method(a, b, c, &_block)
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if unused and there is yield" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
def method(a, b, c, &block)
|
||||
# ^^^^^ error: Use `&` as an argument name to indicate that it won't be referenced.
|
||||
3.times do |i|
|
||||
i.try do
|
||||
yield i
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
def method(a, b, c, &)
|
||||
3.times do |i|
|
||||
i.try do
|
||||
yield i
|
||||
end
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if anonymous and there is yield" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c, &)
|
||||
yield 1
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if variable is referenced implicitly" do
|
||||
s = Source.new %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
def method(a, b, c, &block)
|
||||
super
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "super" do
|
||||
it "reports if variable is not referenced implicitly by super" do
|
||||
s = Source.new %(
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
def method(a, b, c, &block)
|
||||
# ^^^^^ error: Unused block argument `block`. [...]
|
||||
super a, b, c
|
||||
end
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
s.issues.first.message.should eq "Unused block argument `block`. If it's necessary, " \
|
||||
"use `_block` as an argument name to indicate " \
|
||||
"that it won't be used."
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
def method(a, b, c, &_block)
|
||||
super a, b, c
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "macro" 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)
|
||||
{% block %}
|
||||
end
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,22 +24,5 @@ module Ameba::Rule::Lint
|
|||
end
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -17,7 +17,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is select followed by any? without a block" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
[1, 2, 3].select { |e| e > 2 }.any?
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `select {...}.any?`
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `select {...}.any?`
|
||||
CRYSTAL
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
expect_no_corrections source
|
||||
|
@ -46,8 +46,8 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure object_call_names" do
|
||||
rule = Rule::Performance::AnyAfterFilter.new
|
||||
it "#filter_names" do
|
||||
rule = AnyAfterFilter.new
|
||||
rule.filter_names = %w(select)
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
|
@ -60,20 +60,11 @@ module Ameba::Rule::Performance
|
|||
it "reports in macro scope" 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
|
||||
|
||||
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?`
|
||||
CRYSTAL
|
||||
|
||||
expect_no_corrections source
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,14 +14,10 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
|
||||
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?
|
||||
# ^^^^ error: Use `!{...}.empty?` instead of `{...}.any?`
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
![1, 2, 3].empty?
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "does not report if source is a spec" do
|
||||
|
@ -32,28 +28,11 @@ module Ameba::Rule::Performance
|
|||
|
||||
context "macro" do
|
||||
it "reports in macro scope" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
{{ [1, 2, 3].any? }}
|
||||
# ^^^^ error: Use `!{...}.empty?` instead of `{...}.any?`
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
{{ ![1, 2, 3].empty? }}
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -44,7 +44,7 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure `call_names`" do
|
||||
it "#call_names" do
|
||||
rule = ChainedCallWithNoBang.new
|
||||
rule.call_names = %w(uniq)
|
||||
|
||||
|
@ -54,22 +54,6 @@ module Ameba::Rule::Performance
|
|||
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
|
||||
it "doesn't report in macro scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
|
|
|
@ -19,7 +19,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is map followed by compact call" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
(1..3).map(&.itself).compact
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^ error: Use `compact_map {...}` instead of `map {...}.compact`
|
||||
# ^^^^^^^^^^^^^^^^^^^^^ error: Use `compact_map {...}` instead of `map {...}.compact`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -36,18 +36,5 @@ module Ameba::Rule::Performance
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -17,7 +17,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is select followed by last" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[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
|
||||
end
|
||||
|
||||
|
@ -30,14 +30,14 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is select followed by last?" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[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
|
||||
end
|
||||
|
||||
it "reports if there is select followed by first" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[1, 2, 3].select { |e| e > 2 }.first
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first`
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -50,7 +50,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is select followed by first?" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[1, 2, 3].select { |e| e > 2 }.first?
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first?`
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first?`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -62,8 +62,8 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure object_call_names" do
|
||||
rule = Rule::Performance::FirstLastAfterFilter.new
|
||||
it "#filter_names" do
|
||||
rule = FirstLastAfterFilter.new
|
||||
rule.filter_names = %w(reject)
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
|
@ -72,21 +72,6 @@ module Ameba::Rule::Performance
|
|||
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
|
||||
it "doesn't report in macro scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
|
|
|
@ -13,7 +13,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is map followed by flatten call" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
%w[Alice Bob].map(&.chars).flatten
|
||||
# ^^^^^^^^^^^^^^^^^^^^^ error: Use `flat_map {...}` instead of `map {...}.flatten`
|
||||
# ^^^^^^^^^^^^^^^^^^^^ error: Use `flat_map {...}` instead of `map {...}.flatten`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -30,18 +30,5 @@ module Ameba::Rule::Performance
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -14,7 +14,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is map followed by sum without a block" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
(1..3).map(&.to_u64).sum
|
||||
# ^^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
|
||||
# ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -27,14 +27,14 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is map followed by sum without a block (with argument)" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
(1..3).map(&.to_u64).sum(0)
|
||||
# ^^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
|
||||
# ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is map followed by sum with a block" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
(1..3).map(&.to_u64).sum(&.itself)
|
||||
# ^^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
|
||||
# ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -45,18 +45,5 @@ module Ameba::Rule::Performance
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -19,7 +19,7 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is a select followed by size" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[1, 2, 3].select { |e| e > 2 }.size
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `select {...}.size`.
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `select {...}.size`.
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -32,20 +32,20 @@ module Ameba::Rule::Performance
|
|||
it "reports if there is a reject followed by size" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[1, 2, 3].reject { |e| e < 2 }.size
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`.
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`.
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if a block shorthand used" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
[1, 2, 3].reject(&.empty?).size
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`.
|
||||
# ^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`.
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure object caller names" do
|
||||
rule = Rule::Performance::SizeAfterFilter.new
|
||||
it "#filter_names" do
|
||||
rule = SizeAfterFilter.new
|
||||
rule.filter_names = %w(select)
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
|
@ -61,18 +61,5 @@ module Ameba::Rule::Performance
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -3,10 +3,10 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
subject = Rule::Style::ConstantNames.new
|
||||
|
||||
private def it_reports_constant(name, value, expected)
|
||||
it "reports constant name #{expected}" do
|
||||
private def it_reports_constant(name, value, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports constant name #{expected}", file, line do
|
||||
rule = Rule::Style::ConstantNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name
|
||||
expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
|
||||
%{name} = #{value}
|
||||
# ^{name} error: Constant name should be screaming-cased: #{expected}, not #{name}
|
||||
CRYSTAL
|
||||
|
@ -17,7 +17,7 @@ module Ameba
|
|||
it "passes if type names are screaming-cased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
LUCKY_NUMBERS = [3, 7, 11]
|
||||
DOCUMENTATION_URL = "http://crystal-lang.org/docs"
|
||||
DOCUMENTATION_URL = "https://crystal-lang.org/docs"
|
||||
|
||||
Int32
|
||||
|
||||
|
@ -37,19 +37,5 @@ module Ameba
|
|||
# it_reports_constant "MyBadConstant", "1", "MYBADCONSTANT"
|
||||
it_reports_constant "Wrong_NAME", "2", "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
|
||||
|
|
|
@ -3,11 +3,11 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
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
|
||||
|
||||
it "reports an issue if method body is if / unless without else" do
|
||||
source = expect_issue rule, <<-CRYSTAL, line: line
|
||||
it "reports an issue if method body is if / unless without else", file, line do
|
||||
source = expect_issue rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
# ^^ error: Use a guard clause (`return unless something`) instead of wrapping the code inside a conditional expression.
|
||||
|
@ -23,7 +23,7 @@ module Ameba
|
|||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL, line: line
|
||||
expect_correction source, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
return unless something
|
||||
#{body}
|
||||
|
@ -38,8 +38,8 @@ module Ameba
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports an issue if method body ends with if / unless without else" do
|
||||
source = expect_issue rule, <<-CRYSTAL, line: line
|
||||
it "reports an issue if method body ends with if / unless without else", file, line do
|
||||
source = expect_issue rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
test
|
||||
if something
|
||||
|
@ -57,7 +57,7 @@ module Ameba
|
|||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL, line: line
|
||||
expect_correction source, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
test
|
||||
return unless something
|
||||
|
@ -75,11 +75,11 @@ module Ameba
|
|||
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
|
||||
|
||||
it "reports an issue with #{kw} in the if branch" do
|
||||
source = expect_issue rule, <<-CRYSTAL, line: line
|
||||
it "reports an issue with #{kw} in the if branch", file, line do
|
||||
source = expect_issue rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
# ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression.
|
||||
|
@ -90,11 +90,11 @@ module Ameba
|
|||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_no_corrections source, line: line
|
||||
expect_no_corrections source, file: file, line: line
|
||||
end
|
||||
|
||||
it "reports an issue with #{kw} in the else branch" do
|
||||
source = expect_issue rule, <<-CRYSTAL, line: line
|
||||
it "reports an issue with #{kw} in the else branch", file, line do
|
||||
source = expect_issue rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
# ^^ error: Use a guard clause (`#{kw} unless something`) instead of wrapping the code inside a conditional expression.
|
||||
|
@ -105,11 +105,11 @@ module Ameba
|
|||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_no_corrections source, line: line
|
||||
expect_no_corrections source, file: file, line: line
|
||||
end
|
||||
|
||||
it "doesn't report an issue if condition has multiple lines" do
|
||||
expect_no_issues rule, <<-CRYSTAL, line: line
|
||||
it "doesn't report an issue if condition has multiple lines", file, line do
|
||||
expect_no_issues rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something &&
|
||||
something_else
|
||||
|
@ -121,8 +121,8 @@ module Ameba
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "does not report an issue if #{kw} is inside elsif" do
|
||||
expect_no_issues rule, <<-CRYSTAL, line: line
|
||||
it "does not report an issue if #{kw} is inside elsif", file, line do
|
||||
expect_no_issues rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
a
|
||||
|
@ -133,8 +133,8 @@ module Ameba
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "does not report an issue if #{kw} is inside if..elsif..else..end" do
|
||||
expect_no_issues rule, <<-CRYSTAL, line: line
|
||||
it "does not report an issue if #{kw} is inside if..elsif..else..end", file, line do
|
||||
expect_no_issues rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
a
|
||||
|
@ -147,8 +147,8 @@ module Ameba
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report an issue if control flow expr has multiple lines" do
|
||||
expect_no_issues rule, <<-CRYSTAL, line: line
|
||||
it "doesn't report an issue if control flow expr has multiple lines", file, line do
|
||||
expect_no_issues rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
#{kw} \\
|
||||
|
@ -161,8 +161,8 @@ module Ameba
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports an issue if non-control-flow branch has multiple lines" do
|
||||
source = expect_issue rule, <<-CRYSTAL, line: line
|
||||
it "reports an issue if non-control-flow branch has multiple lines", file, line do
|
||||
source = expect_issue rule, <<-CRYSTAL, file: file, line: line
|
||||
def func
|
||||
if something
|
||||
# ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression.
|
||||
|
@ -174,7 +174,7 @@ module Ameba
|
|||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_no_corrections source, line: line
|
||||
expect_no_corrections source, file: file, line: line
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -42,9 +42,10 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure filter_names" do
|
||||
it "#filter_names" do
|
||||
rule = IsAFilter.new
|
||||
rule.filter_names = %w(select)
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
[1, 2, nil].reject(&.nil?)
|
||||
CRYSTAL
|
||||
|
@ -58,20 +59,5 @@ module Ameba::Rule::Style
|
|||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -34,19 +34,5 @@ module Ameba::Rule::Style
|
|||
a.nil?
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -3,12 +3,12 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
subject = Rule::Style::LargeNumbers.new
|
||||
|
||||
private def it_transforms(number, expected)
|
||||
it "transforms large number #{number}" do
|
||||
private def it_transforms(number, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "transforms large number #{number}", file, line do
|
||||
rule = Rule::Style::LargeNumbers.new
|
||||
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} error: Large numbers should be written with underscores: #{expected}
|
||||
CRYSTAL
|
||||
|
@ -97,10 +97,12 @@ module Ameba
|
|||
it_transforms "10000_i16", "10_000_i16"
|
||||
it_transforms "10000_i32", "10_000_i32"
|
||||
it_transforms "10000_i64", "10_000_i64"
|
||||
it_transforms "10000_i128", "10_000_i128"
|
||||
|
||||
it_transforms "10000_u16", "10_000_u16"
|
||||
it_transforms "10000_u32", "10_000_u32"
|
||||
it_transforms "10000_u64", "10_000_u64"
|
||||
it_transforms "10000_u128", "10_000_u128"
|
||||
|
||||
it_transforms "123456_f32", "123_456_f32"
|
||||
it_transforms "123456_f64", "123_456_f64"
|
||||
|
@ -117,20 +119,8 @@ module Ameba
|
|||
it_transforms "3.001234", "3.001_234"
|
||||
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
|
||||
it "allows to configure integer min digits" do
|
||||
it "#int_min_digits" do
|
||||
rule = Rule::Style::LargeNumbers.new
|
||||
rule.int_min_digits = 10
|
||||
expect_no_issues rule, %q(1200000)
|
||||
|
|
|
@ -3,10 +3,10 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
subject = Rule::Style::MethodNames.new
|
||||
|
||||
private def it_reports_method_name(name, expected)
|
||||
it "reports method name #{expected}" do
|
||||
private def it_reports_method_name(name, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports method name #{expected}", file, line do
|
||||
rule = Rule::Style::MethodNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name
|
||||
expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
|
||||
def %{name}; end
|
||||
# ^{name} error: Method name should be underscore-cased: #{expected}, not %{name}
|
||||
CRYSTAL
|
||||
|
@ -38,20 +38,5 @@ module Ameba
|
|||
it_reports_method_name "firstName", "first_name"
|
||||
it_reports_method_name "date_of_Birth", "date_of_birth"
|
||||
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
|
||||
|
|
|
@ -53,16 +53,5 @@ module Ameba::Rule::Style
|
|||
end
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -43,7 +43,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
context "#exclude_ternary=" do
|
||||
context "#exclude_ternary" do
|
||||
it "skips ternary control expressions by default" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
(foo > bar) ? true : false
|
||||
|
@ -51,7 +51,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "allows to configure assignments" do
|
||||
rule = Rule::Style::ParenthesesAroundCondition.new
|
||||
rule = ParenthesesAroundCondition.new
|
||||
rule.exclude_ternary = false
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
|
@ -75,7 +75,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
end
|
||||
|
||||
context "#allow_safe_assignment=" do
|
||||
context "#allow_safe_assignment" do
|
||||
it "reports assignments by default" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
if (foo = @foo)
|
||||
|
@ -98,7 +98,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "allows to configure assignments" do
|
||||
rule = Rule::Style::ParenthesesAroundCondition.new
|
||||
rule = ParenthesesAroundCondition.new
|
||||
rule.allow_safe_assignment = true
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
|
|
|
@ -27,24 +27,6 @@ module Ameba::Rule::Style
|
|||
CRYSTAL
|
||||
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
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Image
|
||||
|
|
|
@ -51,7 +51,7 @@ module Ameba::Rule::Style
|
|||
expect_issue subject, <<-CRYSTAL, call: {{ call }}
|
||||
class Foo
|
||||
%{call} foo : Bool = true
|
||||
_{call} # ^ error: Consider using '%{call}?' for 'foo'
|
||||
_{call} # ^^^ error: Consider using '%{call}?' for 'foo'
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
@ -60,7 +60,7 @@ module Ameba::Rule::Style
|
|||
expect_issue subject, <<-CRYSTAL, call: {{ call }}
|
||||
class Foo
|
||||
%{call} foo : Bool
|
||||
_{call} # ^ error: Consider using '%{call}?' for 'foo'
|
||||
_{call} # ^^^ error: Consider using '%{call}?' for 'foo'
|
||||
|
||||
def initialize(@foo = true)
|
||||
end
|
||||
|
|
|
@ -294,24 +294,5 @@ module Ameba::Rule::Style
|
|||
}
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -201,7 +201,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
context "#allow_multi_next=" do
|
||||
context "#allow_multi_next" do
|
||||
it "allows multi next statements by default" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
block do |a, b|
|
||||
|
@ -211,7 +211,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "allows to configure multi next statements" do
|
||||
rule = Rule::Style::RedundantNext.new
|
||||
rule = RedundantNext.new
|
||||
rule.allow_multi_next = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
block do |a, b|
|
||||
|
@ -238,7 +238,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "allows to configure empty next statements" do
|
||||
rule = Rule::Style::RedundantNext.new
|
||||
rule = RedundantNext.new
|
||||
rule.allow_empty_next = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
block do
|
||||
|
|
|
@ -284,7 +284,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
context "#allow_multi_return=" do
|
||||
context "#allow_multi_return" do
|
||||
it "allows multi returns by default" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b)
|
||||
|
@ -294,7 +294,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "allows to configure multi returns" do
|
||||
rule = Rule::Style::RedundantReturn.new
|
||||
rule = RedundantReturn.new
|
||||
rule.allow_multi_return = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
def method(a, b)
|
||||
|
@ -321,7 +321,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "allows to configure empty returns" do
|
||||
rule = Rule::Style::RedundantReturn.new
|
||||
rule = RedundantReturn.new
|
||||
rule.allow_empty_return = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
def method
|
||||
|
|
|
@ -3,10 +3,10 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
subject = Rule::Style::TypeNames.new
|
||||
|
||||
private def it_reports_name(type, name, expected)
|
||||
it "reports type name #{expected}" do
|
||||
private def it_reports_name(type, name, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports type name #{expected}", file, line do
|
||||
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}^^^^ error: Type name should be camelcased: #{expected}, but it was %{name}
|
||||
CRYSTAL
|
||||
|
@ -49,20 +49,5 @@ module Ameba
|
|||
# ^{} error: Type name should be camelcased: NumericValue, but it was Numeric_value
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -13,7 +13,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "fails if unless has else" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
unless something
|
||||
# ^^^^^^^^^^^^^^ error: Favour if over unless with else
|
||||
:one
|
||||
|
@ -21,24 +21,14 @@ module Ameba::Rule::Style
|
|||
:two
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports rule, pos and message" do
|
||||
s = Source.new %(
|
||||
unless something
|
||||
:one
|
||||
else
|
||||
expect_correction source, <<-CRYSTAL
|
||||
if something
|
||||
:two
|
||||
else
|
||||
:one
|
||||
end
|
||||
), "source.cr"
|
||||
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"
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,10 +3,10 @@ require "../../../spec_helper"
|
|||
module Ameba
|
||||
subject = Rule::Style::VariableNames.new
|
||||
|
||||
private def it_reports_var_name(name, value, expected)
|
||||
it "reports variable name #{expected}" do
|
||||
private def it_reports_var_name(name, value, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports variable name #{expected}", file, line do
|
||||
rule = Rule::Style::VariableNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name
|
||||
expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
|
||||
%{name} = #{value}
|
||||
# ^{name} error: Var name should be underscore-cased: #{expected}, not %{name}
|
||||
CRYSTAL
|
||||
|
@ -62,19 +62,5 @@ module Ameba
|
|||
end
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -64,10 +64,12 @@ module Ameba::Rule::Style
|
|||
context "properties" do
|
||||
it "#exclude_calls_with_block" do
|
||||
rule = VerboseBlock.new
|
||||
|
||||
rule.exclude_calls_with_block = true
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
(1..3).in_groups_of(1) { |i| i.map(&.to_s) }
|
||||
CRYSTAL
|
||||
|
||||
rule.exclude_calls_with_block = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
(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
|
||||
rule = VerboseBlock.new
|
||||
|
||||
rule.exclude_multiple_line_blocks = true
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
(1..3).any? do |i|
|
||||
i.odd?
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
rule.exclude_multiple_line_blocks = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
(1..3).any? do |i|
|
||||
|
@ -102,12 +106,14 @@ module Ameba::Rule::Style
|
|||
|
||||
it "#exclude_prefix_operators" do
|
||||
rule = VerboseBlock.new
|
||||
|
||||
rule.exclude_prefix_operators = true
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
(1..3).sum { |i| +i }
|
||||
(1..3).sum { |i| -i }
|
||||
(1..3).sum { |i| ~i }
|
||||
CRYSTAL
|
||||
|
||||
rule.exclude_prefix_operators = false
|
||||
rule.exclude_operators = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
|
@ -128,10 +134,12 @@ module Ameba::Rule::Style
|
|||
|
||||
it "#exclude_operators" do
|
||||
rule = VerboseBlock.new
|
||||
|
||||
rule.exclude_operators = true
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
(1..3).sum { |i| i * 2 }
|
||||
CRYSTAL
|
||||
|
||||
rule.exclude_operators = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
(1..3).sum { |i| i * 2 }
|
||||
|
@ -145,10 +153,12 @@ module Ameba::Rule::Style
|
|||
|
||||
it "#exclude_setters" do
|
||||
rule = VerboseBlock.new
|
||||
|
||||
rule.exclude_setters = true
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
Char::Reader.new("abc").tap { |reader| reader.pos = 0 }
|
||||
CRYSTAL
|
||||
|
||||
rule.exclude_setters = false
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
Char::Reader.new("abc").tap { |reader| reader.pos = 0 }
|
||||
|
@ -163,12 +173,14 @@ module Ameba::Rule::Style
|
|||
it "#max_line_length" do
|
||||
rule = VerboseBlock.new
|
||||
rule.exclude_multiple_line_blocks = false
|
||||
|
||||
rule.max_line_length = 60
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
(1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i|
|
||||
i.to_s.reverse.strip.blank?
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
rule.max_line_length = nil
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
(1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i|
|
||||
|
@ -184,10 +196,12 @@ module Ameba::Rule::Style
|
|||
|
||||
it "#max_length" do
|
||||
rule = VerboseBlock.new
|
||||
|
||||
rule.max_length = 30
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
(1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? }
|
||||
CRYSTAL
|
||||
|
||||
rule.max_length = nil
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
(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
|
||||
rule = VerboseBlock.new
|
||||
rule.exclude_operators = false
|
||||
|
||||
source = expect_issue rule, <<-CRYSTAL
|
||||
(1..3).map { |i| i.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)
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
|
@ -65,6 +65,7 @@ module Ameba
|
|||
rules = [AtoAA.new] of Rule::Base
|
||||
source = Source.new "class A; end", "source.cr"
|
||||
message = "Infinite loop in source.cr caused by Ameba/AtoAA"
|
||||
|
||||
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
||||
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
||||
end
|
||||
|
@ -95,11 +96,11 @@ module Ameba
|
|||
|
||||
it "does not run other rules" do
|
||||
rules = [Rule::Lint::Syntax.new, Rule::Style::ConstantNames.new] of Rule::Base
|
||||
source = Source.new %q(
|
||||
MyBadConstant = 1
|
||||
source = Source.new <<-CRYSTAL
|
||||
MyBadConstant = 1
|
||||
|
||||
when my_bad_syntax
|
||||
)
|
||||
when my_bad_syntax
|
||||
CRYSTAL
|
||||
|
||||
Runner.new(rules, [source], formatter, default_severity).run
|
||||
source.should_not be_valid
|
||||
|
@ -110,9 +111,9 @@ module Ameba
|
|||
context "unneeded disables" do
|
||||
it "reports an issue if such disable exists" do
|
||||
rules = [Rule::Lint::UnneededDisableDirective.new] of Rule::Base
|
||||
source = Source.new %(
|
||||
source = Source.new <<-CRYSTAL
|
||||
a = 1 # ameba:disable LineLength
|
||||
)
|
||||
CRYSTAL
|
||||
|
||||
Runner.new(rules, [source], formatter, default_severity).run
|
||||
source.should_not be_valid
|
||||
|
@ -134,9 +135,7 @@ module Ameba
|
|||
it "writes the explanation if sources are not valid and location found" do
|
||||
io.clear
|
||||
rules = [ErrorRule.new] of Rule::Base
|
||||
source = Source.new %(
|
||||
a = 1
|
||||
), "source.cr"
|
||||
source = Source.new "a = 1", "source.cr"
|
||||
|
||||
runner = Runner.new(rules, [source], formatter, default_severity).run
|
||||
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
|
||||
io.clear
|
||||
rules = [ErrorRule.new] of Rule::Base
|
||||
source = Source.new %(
|
||||
a = 1
|
||||
), "source.cr"
|
||||
source = Source.new "a = 1", "source.cr"
|
||||
|
||||
runner = Runner.new(rules, [source], formatter, default_severity).run
|
||||
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
|
||||
rules = Rule.rules.map &.new.as(Rule::Base)
|
||||
s = Source.new %q(
|
||||
WrongConstant = 5
|
||||
)
|
||||
Runner.new(rules, [s], formatter, default_severity).run.success?.should be_false
|
||||
source = Source.new "WrongConstant = 5"
|
||||
|
||||
Runner.new(rules, [source], formatter, default_severity).run.success?.should be_false
|
||||
end
|
||||
|
||||
it "depends on the level of severity" do
|
||||
|
@ -184,11 +180,11 @@ module Ameba
|
|||
|
||||
it "returns false if issue is disabled" do
|
||||
rules = [NamedRule.new] of Rule::Base
|
||||
source = Source.new %(
|
||||
source = Source.new <<-CRYSTAL
|
||||
def foo
|
||||
bar = 1 # ameba:disable #{NamedRule.name}
|
||||
end
|
||||
)
|
||||
CRYSTAL
|
||||
source.add_issue NamedRule.new, location: {2, 1},
|
||||
message: "Useless assignment"
|
||||
|
||||
|
@ -205,6 +201,7 @@ module Ameba
|
|||
rules = [AtoB.new, BtoA.new]
|
||||
source = Source.new "class A; end", "source.cr"
|
||||
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
|
||||
|
||||
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
||||
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
||||
end
|
||||
|
@ -214,11 +211,12 @@ module Ameba
|
|||
context "if there are multiple offenses in an inspected file" do
|
||||
it "aborts because of an infinite loop" do
|
||||
rules = [AtoB.new, BtoA.new]
|
||||
source = Source.new %(
|
||||
source = Source.new <<-CRYSTAL, "source.cr"
|
||||
class A; end
|
||||
class A_A; end
|
||||
), "source.cr"
|
||||
CRYSTAL
|
||||
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA"
|
||||
|
||||
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
||||
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
||||
end
|
||||
|
@ -231,6 +229,7 @@ module Ameba
|
|||
rules = [ClassToModule.new, ModuleToClass.new, AtoB.new, BtoA.new]
|
||||
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"
|
||||
|
||||
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
||||
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
||||
end
|
||||
|
@ -242,6 +241,7 @@ module Ameba
|
|||
rules = [AtoB.new, BtoC.new, CtoA.new]
|
||||
source = Source.new "class A; end", "source.cr"
|
||||
message = "Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoC -> Ameba/CtoA"
|
||||
|
||||
expect_raises(Runner::InfiniteCorrectionLoopError, message) do
|
||||
Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run
|
||||
end
|
||||
|
|
|
@ -4,46 +4,70 @@ module Ameba
|
|||
describe Source do
|
||||
describe ".new" do
|
||||
it "allows to create a source by code and path" do
|
||||
s = Source.new("code", "path")
|
||||
s.path.should eq "path"
|
||||
s.code.should eq "code"
|
||||
s.lines.should eq ["code"]
|
||||
source = Source.new "code", "path"
|
||||
source.path.should eq "path"
|
||||
source.code.should eq "code"
|
||||
source.lines.should eq ["code"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#fullpath" do
|
||||
it "returns a relative path of the source" do
|
||||
s = Source.new "", "./source_spec.cr"
|
||||
s.fullpath.should contain "source_spec.cr"
|
||||
source = Source.new "", "./source_spec.cr"
|
||||
source.fullpath.should contain "source_spec.cr"
|
||||
end
|
||||
|
||||
it "returns fullpath if path is blank" do
|
||||
s = Source.new "", ""
|
||||
s.fullpath.should_not be_nil
|
||||
source = Source.new "", ""
|
||||
source.fullpath.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#spec?" do
|
||||
it "returns true if the source is a spec file" do
|
||||
s = Source.new "", "./source_spec.cr"
|
||||
s.spec?.should be_true
|
||||
source = Source.new "", "./source_spec.cr"
|
||||
source.spec?.should be_true
|
||||
end
|
||||
|
||||
it "returns false if the source is not a spec file" do
|
||||
s = Source.new "", "./source.cr"
|
||||
s.spec?.should be_false
|
||||
source = Source.new "", "./source.cr"
|
||||
source.spec?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#matches_path?" do
|
||||
it "returns true if source's path is matched" do
|
||||
s = Source.new "", "source.cr"
|
||||
s.matches_path?("source.cr").should be_true
|
||||
source = Source.new "", "source.cr"
|
||||
source.matches_path?("source.cr").should be_true
|
||||
end
|
||||
|
||||
it "returns false if source's path is not matched" do
|
||||
s = Source.new "", "source.cr"
|
||||
s.matches_path?("new_source.cr").should be_false
|
||||
source = Source.new "", "source.cr"
|
||||
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
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
require "../spec_helper"
|
||||
|
||||
module Ameba
|
||||
private def it_tokenizes(str, expected)
|
||||
it "tokenizes #{str}" do
|
||||
([] of String).tap do |token_types|
|
||||
private def it_tokenizes(str, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "tokenizes #{str}", file, line do
|
||||
%w[].tap do |token_types|
|
||||
Tokenizer.new(Source.new str, normalize: false)
|
||||
.run { |token| token_types << token.type.to_s }
|
||||
.should be_true
|
||||
end.should eq expected
|
||||
end.should eq(expected), file: file, line: line
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -286,3 +286,7 @@ end
|
|||
def as_nodes(source)
|
||||
Ameba::TestNodeVisitor.new(as_node source)
|
||||
end
|
||||
|
||||
def trailing_whitespace
|
||||
' '
|
||||
end
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
module Ameba::Rule::Lint
|
||||
# This rule checks for mistyped shorthand assignments.
|
||||
#
|
||||
# # bad
|
||||
# x =- y
|
||||
# x =+ y
|
||||
# x =! y
|
||||
# This is considered invalid:
|
||||
#
|
||||
# # good
|
||||
# x -= y # or x = -y
|
||||
# x += y # or x = +y
|
||||
# x != y # or x = !y
|
||||
# ```
|
||||
# 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
|
||||
# ```
|
||||
#
|
||||
# YAML configuration example:
|
||||
#
|
||||
|
@ -27,9 +33,9 @@ module Ameba::Rule::Lint
|
|||
MSG = "Suspicious assignment detected. Did you mean `%s`?"
|
||||
|
||||
MISTAKES = {
|
||||
"=-" => "-=",
|
||||
"=+" => "+=",
|
||||
"=!" => "!=",
|
||||
"=-": "-=",
|
||||
"=+": "+=",
|
||||
"=!": "!=",
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
|
|
@ -34,19 +34,11 @@ module Ameba::Rule::Lint
|
|||
description "Disallows empty expressions"
|
||||
end
|
||||
|
||||
MSG = "Avoid empty expression %s"
|
||||
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
|
||||
MSG = "Avoid empty expressions"
|
||||
|
||||
def test(source, node : Crystal::Expressions)
|
||||
return unless node.expressions.size == 1 && node.expressions.first.nop?
|
||||
issue_for node, MSG_EXRS
|
||||
issue_for node, MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,7 +63,18 @@ module Ameba::Rule::Lint
|
|||
next if scope.references?(argument.variable)
|
||||
|
||||
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
|
||||
|
|
|
@ -31,6 +31,8 @@ module Ameba::Rule::Lint
|
|||
# Enabled: true
|
||||
# ```
|
||||
class UnusedBlockArgument < Base
|
||||
include AST::Util
|
||||
|
||||
properties do
|
||||
description "Disallows unused block arguments"
|
||||
end
|
||||
|
@ -51,11 +53,26 @@ module Ameba::Rule::Lint
|
|||
return if block_arg.anonymous?
|
||||
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?
|
||||
issue_for block_arg.node, MSG_YIELDED
|
||||
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
|
||||
end
|
||||
else
|
||||
return if block_arg.ignored?
|
||||
issue_for block_arg.node, MSG_UNUSED % block_arg.name
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,14 +42,10 @@ module Ameba::Rule::Performance
|
|||
return unless node.block.nil? && node.args.empty?
|
||||
return unless node.obj
|
||||
|
||||
return unless location = node.location
|
||||
return unless name_location = node.name_location
|
||||
return unless end_location = name_end_location(node)
|
||||
|
||||
issue_for name_location, end_location, MSG do |corrector|
|
||||
corrector.insert_before(location, '!')
|
||||
corrector.replace(name_location, end_location, "empty?")
|
||||
end
|
||||
issue_for name_location, end_location, MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,8 +57,7 @@ module Ameba::Rule::Style
|
|||
|
||||
if cond.is_a?(Crystal::Assign) && allow_safe_assignment?
|
||||
issue_for cond, MSG_MISSING do |corrector|
|
||||
corrector.insert_before(cond, '(')
|
||||
corrector.insert_after(cond, ')')
|
||||
corrector.wrap(cond, '(', ')')
|
||||
end
|
||||
return
|
||||
end
|
||||
|
|
|
@ -50,7 +50,36 @@ module Ameba::Rule::Style
|
|||
MSG = "Favour if over unless with else"
|
||||
|
||||
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
|
||||
|
|
|
@ -130,7 +130,7 @@ module Ameba
|
|||
rule.test(source)
|
||||
end
|
||||
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
|
||||
# only keep the corrected ones in order to avoid duplicate reporting.
|
||||
|
|
|
@ -24,9 +24,10 @@ module Ameba
|
|||
|
||||
# Corrects any correctable issues and updates `code`.
|
||||
# Returns `false` if no issues were corrected.
|
||||
def correct
|
||||
def correct?
|
||||
corrector = Corrector.new(code)
|
||||
issues.each(&.correct(corrector))
|
||||
|
||||
corrected_code = corrector.process
|
||||
return false if code == corrected_code
|
||||
|
||||
|
@ -75,5 +76,13 @@ module Ameba
|
|||
def matches_path?(filepath)
|
||||
path.in?(filepath, File.expand_path(filepath))
|
||||
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
|
||||
|
|
|
@ -18,41 +18,92 @@ class Ameba::Source
|
|||
@rewriter.replace(loc_to_pos(location), loc_to_pos(end_location) + 1, content)
|
||||
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.
|
||||
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)
|
||||
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, "")`
|
||||
def remove(location, end_location)
|
||||
@rewriter.remove(loc_to_pos(location), loc_to_pos(end_location) + 1)
|
||||
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)`
|
||||
def insert_before(location, end_location, content)
|
||||
@rewriter.insert_before(loc_to_pos(location), loc_to_pos(end_location) + 1, content)
|
||||
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)`
|
||||
def insert_after(location, end_location, content)
|
||||
@rewriter.insert_after(loc_to_pos(location), loc_to_pos(end_location) + 1, content)
|
||||
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)`
|
||||
def insert_before(location, content)
|
||||
@rewriter.insert_before(loc_to_pos(location), content)
|
||||
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)`
|
||||
def insert_after(location, content)
|
||||
@rewriter.insert_after(loc_to_pos(location) + 1, content)
|
||||
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.
|
||||
def remove_preceding(location, end_location, size)
|
||||
@rewriter.remove(loc_to_pos(location) - size, loc_to_pos(location))
|
||||
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.
|
||||
# If *size* is greater than the size of the range, the removed region can
|
||||
# overrun the end of the range.
|
||||
|
@ -60,6 +111,12 @@ class Ameba::Source
|
|||
@rewriter.remove(loc_to_pos(location), loc_to_pos(location) + size)
|
||||
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.
|
||||
# If *size* is greater than the size of the range, the removed region can
|
||||
# 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)
|
||||
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})
|
||||
if location.is_a?(Crystal::Location)
|
||||
line, column = location.line_number, location.column_number
|
||||
|
|
|
@ -125,7 +125,9 @@ class Ameba::Source
|
|||
|
||||
private def check_range_validity(begin_pos, end_pos)
|
||||
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
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
class Ameba::Spec::AnnotatedSource
|
||||
ANNOTATION_PATTERN_1 = /\A\s*(# )?(\^+|\^{})( error:)? /
|
||||
ANNOTATION_PATTERN_2 = " # error: "
|
||||
ABBREV = "[...]"
|
||||
|
||||
ABBREV = "[...]"
|
||||
|
||||
getter lines : Array(String)
|
||||
|
||||
|
@ -15,6 +16,7 @@ class Ameba::Spec::AnnotatedSource
|
|||
def self.parse(annotated_code)
|
||||
lines = [] of String
|
||||
annotations = [] of {Int32, String, String}
|
||||
|
||||
code_lines = annotated_code.split('\n') # must preserve trailing newline
|
||||
code_lines.each do |code_line|
|
||||
case
|
||||
|
@ -39,7 +41,9 @@ class Ameba::Spec::AnnotatedSource
|
|||
# NOTE: Annotations are sorted so that reconstructing the annotation
|
||||
# text via `#to_s` is deterministic.
|
||||
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
|
||||
|
||||
# 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
|
||||
# text via `#to_s` is deterministic.
|
||||
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
|
||||
|
||||
def ==(other)
|
||||
|
|
|
@ -13,8 +13,8 @@ module Ameba::Spec
|
|||
def failure_message(source)
|
||||
String.build do |str|
|
||||
str << "Source expected to be valid, but there are issues: \n\n"
|
||||
source.issues.reject(&.disabled?).each do |e|
|
||||
str << " * #{e.rule.name}: #{e.message}\n"
|
||||
source.issues.reject(&.disabled?).each do |issue|
|
||||
str << " * #{issue.rule.name}: #{issue.message}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -129,7 +129,7 @@ module Ameba::Spec::ExpectIssue
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
fail <<-MSG, file, line
|
||||
|
@ -144,7 +144,7 @@ module Ameba::Spec::ExpectIssue
|
|||
end
|
||||
|
||||
def expect_no_corrections(source, *, file = __FILE__, line = __LINE__)
|
||||
return unless source.correct
|
||||
return unless source.correct?
|
||||
|
||||
fail <<-MSG, file, line
|
||||
Expected no corrections, but got:
|
||||
|
|
|
@ -14,9 +14,5 @@ module Ameba
|
|||
end
|
||||
end
|
||||
|
||||
def trailing_whitespace
|
||||
' '
|
||||
end
|
||||
|
||||
include Ameba::Spec::BeValid
|
||||
include Ameba::Spec::ExpectIssue
|
||||
|
|
Loading…
Reference in a new issue