mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Compare commits
373 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a42b218ca6 | ||
|
a836e2c8d0 | ||
|
e0fa8bbcc2 | ||
|
c4cc71e248 | ||
|
6d03cef6df | ||
|
f12e7f6c5d | ||
|
1bd59c1bf0 | ||
|
5403aee899 | ||
|
e6a5fa9d71 | ||
|
a3f906a38a | ||
|
107c6e0ea6 | ||
|
a661cf10fc | ||
|
a2c9aa67cc | ||
|
e2d6c69039 | ||
|
63be60ce96 | ||
|
17084f4a1d | ||
|
590640b559 | ||
|
3bea264948 | ||
|
7f50ff90fd | ||
|
a79e711fae | ||
|
28fafea19f | ||
|
f2677d68f6 | ||
|
b56d34715d | ||
|
1398c0ee8f | ||
|
734bb2a7f1 | ||
|
98d5bc720a | ||
|
d23ad7f0ab | ||
|
b6bd74e02f | ||
|
ce3f2b7e4b | ||
|
444b07c179 | ||
|
e99a69765f | ||
|
6d0b12c70f | ||
|
65ab317a3b | ||
|
452a7a867e | ||
|
5a24f1eba5 | ||
|
aeffa6ad00 | ||
|
4567293add | ||
|
a49faa33a9 | ||
|
1dd531740c | ||
|
1b661d633d | ||
|
9745637cf9 | ||
|
4ad151e5e0 | ||
|
c9bc01f88c | ||
|
1feb5c279b | ||
|
57898fd797 | ||
|
46a42ee9e8 | ||
|
61afa5bb2b | ||
|
9bb6c9ac75 | ||
|
954345d316 | ||
|
55f3ec53b7 | ||
|
26d9bc0bd0 | ||
|
47088b10ca | ||
|
9f9d5fae32 | ||
|
5e70ae4f8c | ||
|
82e0e53080 | ||
|
1b8523def6 | ||
|
a88033c8ce | ||
|
30e3816ed1 | ||
|
5aac63ea74 | ||
|
10b577d23a | ||
|
06dc201344 | ||
|
d079f4bae6 | ||
|
0461fff702 | ||
|
22e2d1de00 | ||
|
810a3440dd | ||
|
f3f1f3a2ab | ||
|
547fec5a94 | ||
|
a8b8c35cc7 | ||
|
11bf9ffcdc | ||
|
52ccf23ef9 | ||
|
b3f11913ed | ||
|
633ed7538e | ||
|
15d241e138 | ||
|
52a3e47a3b | ||
|
3b87aa6490 | ||
|
018adb54be | ||
|
be76b3682a | ||
|
775650c882 | ||
|
21a406e56d | ||
|
0b225da9ba | ||
|
0a2609c1b4 | ||
|
06952fc7d3 | ||
|
f984d83b05 | ||
|
98cc6fd612 | ||
|
6caf24ad6d | ||
|
e62fffae80 | ||
|
61ccb030bd | ||
|
971bff6c27 | ||
|
bf4219532f | ||
|
a40f02f77f | ||
|
bee4472a26 | ||
|
28014ada67 | ||
|
1d76a7c71a | ||
|
0abb73f0b6 | ||
|
fd44eeba08 | ||
|
cc23e7a7e7 | ||
|
964d011d53 | ||
|
3f1e925e07 | ||
|
e84cc05f0f | ||
|
7ceb3ffad9 | ||
|
b9ce705a47 | ||
|
881209d54e | ||
|
bcb72fb3c3 | ||
|
b25dc402c8 | ||
|
8569355b5a | ||
|
0c6745781e | ||
|
891cad2610 | ||
|
0140fd3573 | ||
|
9f6615bdfd | ||
|
1fccbfc8b8 | ||
|
c2b5e9449c | ||
|
d5ac394d19 | ||
|
bdbb79f1fa | ||
|
1b342e8257 | ||
|
23c61e04c0 | ||
|
ddb6e3c38f | ||
|
ef16ad6471 | ||
|
1b57e2cad5 | ||
|
3d3626accc | ||
|
bede3f97a1 | ||
|
8ff621ba66 | ||
|
f1f21ac94d | ||
|
1718945523 | ||
|
c9538220c6 | ||
|
789e1b77e8 | ||
|
7174e81a13 | ||
|
29f84921b5 | ||
|
c7f3fe78aa | ||
|
2d9db35ec4 | ||
|
dfda3d7677 | ||
|
0829f70256 | ||
|
53b311c5eb | ||
|
867ddb4fbd | ||
|
6724f9a0e0 | ||
|
6389edc5fa | ||
|
0ab39a025b | ||
|
135ff87c7e | ||
|
18d193bd08 | ||
|
f96cb01015 | ||
|
1b85ba6f22 | ||
|
eb60b25c4e | ||
|
7690074cab | ||
|
7b8316f061 | ||
|
b2069ea4ff | ||
|
e85531df6c | ||
|
07aebfc84a | ||
|
8ef588dc6d | ||
|
3b9c442e09 | ||
|
88e0437902 | ||
|
4741c9f4c4 | ||
|
d9b2d69055 | ||
|
5f878fb40f | ||
|
01a943d0d6 | ||
|
8c9d234d0b | ||
|
efa9c9dba0 | ||
|
15ce5437d1 | ||
|
eacb9308a7 | ||
|
a33f98624a | ||
|
33c8273866 | ||
|
327ed546b9 | ||
|
ddff8d226b | ||
|
5cff76071a | ||
|
29e29b8e1d | ||
|
21051acfff | ||
|
abe5237802 | ||
|
b7b21ffeb0 | ||
|
4d0125a0f3 | ||
|
e1f5c81804 | ||
|
16141a376e | ||
|
596b0dd9d0 | ||
|
b4244d4c61 | ||
|
1931a5f4ef | ||
|
c09b36799a | ||
|
38b6751bc0 | ||
|
db59b23f9b | ||
|
9a8538aa69 | ||
|
aceb054aa0 | ||
|
b156a6a6a1 | ||
|
94e31d4685 | ||
|
e12d72cc88 | ||
|
262e31c35b | ||
|
c9d25f3409 | ||
|
7caa47fb6a | ||
|
4d8346509e | ||
|
4c740f394a | ||
|
85c3db4d74 | ||
|
6e5a9a60b3 | ||
|
09fdac6be9 | ||
|
1a9a58b3cd | ||
|
60948fffd0 | ||
|
d0d8b18c83 | ||
|
14f6ba0c0b | ||
|
149080ae16 | ||
|
454a747a68 | ||
|
ef2d05e48a | ||
|
c7f2cba409 | ||
|
7c74d196d6 | ||
|
adac90c7c6 | ||
|
239f64c278 | ||
|
e2528d93dd | ||
|
48c7a2bde6 | ||
|
d45285d1c9 | ||
|
3fbbe3986e | ||
|
4c59858f25 | ||
|
8d56f22af1 | ||
|
e481a8d139 | ||
|
102e2834b6 | ||
|
749da0984c | ||
|
9534104942 | ||
|
6f05df4006 | ||
|
81177dc7e4 | ||
|
d03f058cae | ||
|
be8862837f | ||
|
63a54986dd | ||
|
801affc58b | ||
|
c9c5fb655f | ||
|
6717ac7b70 | ||
|
17e9566c7e | ||
|
031c1daf58 | ||
|
a80672730c | ||
|
8f5d2cca6e | ||
|
e7f4bb6ae3 | ||
|
61a45d4321 | ||
|
d20cc212b9 | ||
|
6b2ddcb1d9 | ||
|
14a9ec3a75 | ||
|
ddbcf5cb3f | ||
|
de5c6ad4c6 | ||
|
00e39f6c41 | ||
|
bd68e8c3b3 | ||
|
8fbaf302ca | ||
|
2c044f8179 | ||
|
0efaed9091 | ||
|
7be5af216e | ||
|
ff79cefaa8 | ||
|
7d0fcd4b87 | ||
|
a88299fa5c | ||
|
74407cc8f5 | ||
|
ce4dd7236a | ||
|
e58f90009f | ||
|
3b79392ef2 | ||
|
8e86374d08 | ||
|
d31e41b8a7 | ||
|
a091af14a8 | ||
|
6f0b6ffcd0 | ||
|
f7cb5bb563 | ||
|
1514fc53ca | ||
|
853f84d954 | ||
|
c5748aec71 | ||
|
1ebc1a68c6 | ||
|
89f995f211 | ||
|
e9226c05d5 | ||
|
01ab89b348 | ||
|
1a60cc3c18 | ||
|
34620a986d | ||
|
d5fbb07c3a | ||
|
caaf803ecd | ||
|
bbbfdfc5a2 | ||
|
2d9e328d97 | ||
|
2cedd72b09 | ||
|
4f8b79ec6b | ||
|
3586b4242f | ||
|
f26cd7f823 | ||
|
f1b270dfbf | ||
|
ea4439d6e2 | ||
|
c02f7fdc33 | ||
|
b9bc5aaab2 | ||
|
232c16d743 | ||
|
cfea0fda34 | ||
|
9926f0295a | ||
|
1ba6fcb55c | ||
|
e9f3bbaeff | ||
|
7c617b5a7b | ||
|
5ff8d2593a | ||
|
e76de37b7d | ||
|
2cfbe97b6d | ||
|
6aa36f3d9e | ||
|
206b5ab604 | ||
|
1af70749b8 | ||
|
cabf203e0d | ||
|
b78e2aebc5 | ||
|
47b92fbb76 | ||
|
597372c645 | ||
|
d7154ad6d2 | ||
|
3bc8bda008 | ||
|
4e3caf2986 | ||
|
ab059616b5 | ||
|
70078cf77f | ||
|
8112dddc8f | ||
|
d7795c0d7d | ||
|
e6ebca7a5b | ||
|
bb0c0eeec6 | ||
|
b6f3d41211 | ||
|
7f6bd2289e | ||
|
858557bc07 | ||
|
6ffb635dcc | ||
|
4b1378aa33 | ||
|
9df66e890b | ||
|
4533e52aa5 | ||
|
4d9cd4d56c | ||
|
a8fca61b22 | ||
|
99c81d3630 | ||
|
2dc21a00d9 | ||
|
0bd4ed0c45 | ||
|
1261f4ba6d | ||
|
db3ef762b9 | ||
|
2fb453c61f | ||
|
07ce595ef2 | ||
|
784e3ac616 | ||
|
5ab4b05add | ||
|
7d88455b7f | ||
|
071a8b7afb | ||
|
2cb8c1381f | ||
|
150ba6c70f | ||
|
e18da84ede | ||
|
cdf0405496 | ||
|
f45d6a2ef0 | ||
|
1524aad299 | ||
|
1818bcfd27 | ||
|
4ec829e83d | ||
|
be65ba2a92 | ||
|
4dd62a3ed1 | ||
|
2113e8c055 | ||
|
a8f953a2b2 | ||
|
b79f3e6e07 | ||
|
134963ece7 | ||
|
523a622b34 | ||
|
5502f0f8d1 | ||
|
748cab29b4 | ||
|
629e65127d | ||
|
adff510cb6 | ||
|
33ef9c6293 | ||
|
f9b05a309e | ||
|
0f893971dc | ||
|
20935ae381 | ||
|
d19d3b78c4 | ||
|
f89e7c2d3c | ||
|
6a180757f3 | ||
|
1399aa3cdf | ||
|
75482a06cf | ||
|
3b7cd3723c | ||
|
e7a6b6b153 | ||
|
735ec2462a | ||
|
38eb5d5e50 | ||
|
2c67fe2c3f | ||
|
8b43a40a65 | ||
|
ffd63ef028 | ||
|
4500181ddb | ||
|
60813e4899 | ||
|
9f670c09b5 | ||
|
2af58cabd4 | ||
|
c7c75ee36a | ||
|
31392046e0 | ||
|
39cc286263 | ||
|
6bd18f9cbf | ||
|
a3e5f2d206 | ||
|
5ee4074c1b | ||
|
0b0a815c31 | ||
|
eabe463386 | ||
|
94a271b2a1 | ||
|
496e8930e2 | ||
|
243071700d | ||
|
95d68114c7 | ||
|
f9b6b17657 | ||
|
5c08b64e72 | ||
|
28e2871165 | ||
|
b5ac5990ec | ||
|
935296b041 | ||
|
e54029d8ed | ||
|
76a4209706 | ||
|
2fb37da80f | ||
|
3340613a6a | ||
|
cbf5d3de74 |
225 changed files with 5725 additions and 2670 deletions
7
.ameba.yml
Normal file
7
.ameba.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
Documentation/DocumentationAdmonition:
|
||||
Timezone: UTC
|
||||
Admonitions: [FIXME, BUG]
|
||||
|
||||
Lint/Typos:
|
||||
Excluded:
|
||||
- spec/ameba/rule/lint/typos_spec.cr
|
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@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into ${{ env.REGISTRY }} registry
|
||||
uses: docker/login-action@v3
|
||||
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@v5
|
||||
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@v6
|
||||
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 }}
|
24
.github/workflows/ci.yml
vendored
24
.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
|
||||
|
||||
|
@ -16,21 +18,29 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Set timezone to UTC
|
||||
uses: szenius/set-timezone@v2.0
|
||||
|
||||
- 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@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: shards install
|
||||
env:
|
||||
SHARDS_OPTS: --ignore-crystal-version
|
||||
|
||||
- name: Install typos-cli
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install typos-cli
|
||||
|
||||
- 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
|
||||
|
|
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@v4
|
||||
|
||||
- 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,13 +1,14 @@
|
|||
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 pcre2 gc libevent libgcc
|
||||
RUN mkdir /src
|
||||
WORKDIR /src
|
||||
COPY --from=builder /ameba/bin/ameba /usr/bin/
|
||||
RUN ameba -v
|
||||
ENTRYPOINT [ "/usr/bin/ameba" ]
|
||||
|
|
96
Makefile
96
Makefile
|
@ -1,22 +1,92 @@
|
|||
.POSIX:
|
||||
all:
|
||||
|
||||
# Recipes
|
||||
|
||||
## Build ameba
|
||||
## $ make
|
||||
## Run tests
|
||||
## $ make test
|
||||
## Install ameba
|
||||
## $ sudo make install
|
||||
|
||||
-include Makefile.local # for optional local options
|
||||
|
||||
BUILD_TARGET ::= bin/ameba
|
||||
|
||||
DESTDIR ?= ## Install destination dir
|
||||
PREFIX ?= /usr/local## Install path prefix
|
||||
BINDIR ?= $(DESTDIR)$(PREFIX)/bin
|
||||
|
||||
# The crystal command to use
|
||||
CRYSTAL_BIN ?= crystal
|
||||
# The shards command to use
|
||||
SHARDS_BIN ?= shards
|
||||
PREFIX ?= /usr/local
|
||||
# The install command to use
|
||||
INSTALL_BIN ?= /usr/bin/install
|
||||
|
||||
SHARD_BIN ?= ../../bin
|
||||
CRFLAGS ?= -Dpreview_mt
|
||||
|
||||
build: bin/ameba
|
||||
bin/ameba:
|
||||
SRC_SOURCES ::= $(shell find src -name '*.cr' 2>/dev/null)
|
||||
DOC_SOURCE ::= src/**
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build the application binary
|
||||
build: $(BUILD_TARGET)
|
||||
|
||||
$(BUILD_TARGET): $(SRC_SOURCES)
|
||||
$(SHARDS_BIN) build $(CRFLAGS)
|
||||
|
||||
docs: ## Generate API docs
|
||||
docs: $(SRC_SOURCES)
|
||||
$(CRYSTAL_BIN) docs -o docs $(DOC_SOURCE)
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run ameba on ameba's code base
|
||||
lint: $(BUILD_TARGET)
|
||||
$(BUILD_TARGET)
|
||||
|
||||
.PHONY: spec
|
||||
spec: ## Run the spec suite
|
||||
spec:
|
||||
$(CRYSTAL_BIN) spec
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Remove application binary
|
||||
clean:
|
||||
rm -f ./bin/ameba ./bin/ameba.dwarf
|
||||
install: build
|
||||
mkdir -p $(PREFIX)/bin
|
||||
cp ./bin/ameba $(PREFIX)/bin
|
||||
@rm -f "$(BUILD_TARGET)" "$(BUILD_TARGET).dwarf"
|
||||
|
||||
.PHONY: install
|
||||
install: ## Install application binary into $DESTDIR
|
||||
install: $(BUILD_TARGET)
|
||||
$(INSTALL_BIN) -m 0755 "$(BUILD_TARGET)" "$(BINDIR)/ameba"
|
||||
|
||||
.PHONY: bin
|
||||
bin: build
|
||||
mkdir -p $(SHARD_BIN)
|
||||
cp ./bin/ameba $(SHARD_BIN)
|
||||
run_file:
|
||||
cp -n ./bin/ameba.cr $(SHARD_BIN) || true
|
||||
test: build
|
||||
$(CRYSTAL_BIN) spec
|
||||
./bin/ameba --all
|
||||
cp $(BUILD_TARGET) $(SHARD_BIN)
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run the spec suite and linter
|
||||
test: spec lint
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this help
|
||||
@echo
|
||||
@printf '\033[34mtargets:\033[0m\n'
|
||||
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\
|
||||
sort |\
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
||||
@echo
|
||||
@printf '\033[34moptional variables:\033[0m\n'
|
||||
@grep -hE '^[a-zA-Z_-]+ \?=.*?## .*$$' $(MAKEFILE_LIST) |\
|
||||
sort |\
|
||||
awk 'BEGIN {FS = " \\?=.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
||||
@echo
|
||||
@printf '\033[34mrecipes:\033[0m\n'
|
||||
@grep -hE '^##.*$$' $(MAKEFILE_LIST) |\
|
||||
awk 'BEGIN {FS = "## "}; /^## [a-zA-Z_-]/ {printf " \033[36m%s\033[0m\n", $$2}; /^## / {printf " %s\n", $$2}'
|
||||
|
|
130
README.md
130
README.md
|
@ -1,5 +1,3 @@
|
|||
[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/veelenga/bin/master/ameba/logo.png" width="800">
|
||||
<h3 align="center">Ameba</h3>
|
||||
|
@ -10,29 +8,29 @@
|
|||
</sup>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/crystal-ameba/ameba/actions?query=workflow%3ACI"><img src="https://github.com/crystal-ameba/ameba/workflows/CI/badge.svg"></a>
|
||||
<a href="https://github.com/crystal-ameba/ameba/actions/workflows/ci.yml"><img src="https://github.com/crystal-ameba/ameba/actions/workflows/ci.yml/badge.svg"></a>
|
||||
<a href="https://github.com/crystal-ameba/ameba/releases"><img src="https://img.shields.io/github/release/crystal-ameba/ameba.svg?maxAge=360"></a>
|
||||
<a href="https://github.com/crystal-ameba/ameba/blob/master/LICENSE"><img src="https://img.shields.io/github/license/crystal-ameba/ameba.svg"></a>
|
||||
<a href="https://gitter.im/veelenga/ameba?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"><img src="https://badges.gitter.im/veelenga/ameba.svg"></a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
- [About](#about)
|
||||
- [Usage](#usage)
|
||||
* [Watch a tutorial](#watch-a-tutorial)
|
||||
* [Run in parallel](#run-in-parallel)
|
||||
- [Watch a tutorial](#watch-a-tutorial)
|
||||
- [Autocorrection](#autocorrection)
|
||||
- [Explain issues](#explain-issues)
|
||||
- [Run in parallel](#run-in-parallel)
|
||||
- [Installation](#installation)
|
||||
* [As a project dependency:](#as-a-project-dependency)
|
||||
* [OS X](#os-x)
|
||||
* [Docker](#docker)
|
||||
* [From sources](#from-sources)
|
||||
- [As a project dependency:](#as-a-project-dependency)
|
||||
- [OS X](#os-x)
|
||||
- [Docker](#docker)
|
||||
- [From sources](#from-sources)
|
||||
- [Configuration](#configuration)
|
||||
* [Sources](#sources)
|
||||
* [Rules](#rules)
|
||||
* [Explain issues](#explain-issues)
|
||||
* [Inline disabling](#inline-disabling)
|
||||
- [Editors & integrations](#editors--integrations)
|
||||
- [Credits & inspirations](#credits--inspirations)
|
||||
- [Sources](#sources)
|
||||
- [Rules](#rules)
|
||||
- [Inline disabling](#inline-disabling)
|
||||
- [Editors \& integrations](#editors--integrations)
|
||||
- [Credits \& inspirations](#credits--inspirations)
|
||||
- [Contributors](#contributors)
|
||||
|
||||
## About
|
||||
|
@ -51,20 +49,25 @@ Run `ameba` binary within your project directory to catch code issues:
|
|||
$ ameba
|
||||
Inspecting 107 files
|
||||
|
||||
...............F.....................F....................................................................
|
||||
...............F.....................FF....................................................................
|
||||
|
||||
src/ameba/formatter/flycheck_formatter.cr:4:33
|
||||
[W] Lint/UnusedArgument: Unused argument `location`
|
||||
> source.issues.each do |e, location|
|
||||
src/ameba/formatter/flycheck_formatter.cr:6:37
|
||||
[W] Lint/UnusedArgument: Unused argument `location`. If it's necessary, use `_` as an argument name to indicate that it won't be used.
|
||||
> source.issues.each do |issue, location|
|
||||
^
|
||||
|
||||
src/ameba/formatter/base_formatter.cr:12:7
|
||||
src/ameba/formatter/base_formatter.cr:16:14
|
||||
[W] Lint/UselessAssign: Useless assignment to variable `s`
|
||||
> return s += issues.size
|
||||
^
|
||||
|
||||
Finished in 542.64 milliseconds
|
||||
129 inspected, 2 failures
|
||||
src/ameba/formatter/base_formatter.cr:16:7 [Correctable]
|
||||
[C] Style/RedundantReturn: Redundant `return` detected
|
||||
> return s += issues.size
|
||||
^---------------------^
|
||||
|
||||
Finished in 389.45 milliseconds
|
||||
107 inspected, 3 failures
|
||||
```
|
||||
|
||||
### Watch a tutorial
|
||||
|
@ -73,17 +76,29 @@ Finished in 542.64 milliseconds
|
|||
|
||||
[🎬 Watch the LuckyCast showing how to use Ameba](https://luckycasts.com/videos/ameba)
|
||||
|
||||
### Run in parallel
|
||||
### Autocorrection
|
||||
|
||||
Starting from 0.31.0 Crystal [supports parallelism](https://crystal-lang.org/2019/09/06/parallelism-in-crystal.html).
|
||||
It allows to run linting in parallel too.
|
||||
In order to take advantage of this feature you need to build ameba with preview_mt support:
|
||||
Rules that are marked as `[Correctable]` in the output can be automatically corrected using `--fix` flag:
|
||||
|
||||
```sh
|
||||
$ crystal build src/cli.cr -Dpreview_mt -o bin/ameba
|
||||
$ make install
|
||||
$ ameba --fix
|
||||
```
|
||||
|
||||
### Explain issues
|
||||
|
||||
Ameba allows you to dig deeper into an issue, by showing you details about the issue
|
||||
and the reasoning by it being reported.
|
||||
|
||||
To be convenient, you can just copy-paste the `PATH:line:column` string from the
|
||||
report and paste behind the `ameba` command to check it out.
|
||||
|
||||
```sh
|
||||
$ ameba crystal/command/format.cr:26:83 # show explanation for the issue
|
||||
$ ameba --explain crystal/command/format.cr:26:83 # same thing
|
||||
```
|
||||
|
||||
### Run in parallel
|
||||
|
||||
Some quick benchmark results measured while running Ameba on Crystal repo:
|
||||
|
||||
```sh
|
||||
|
@ -103,30 +118,14 @@ Add this to your application's `shard.yml`:
|
|||
development_dependencies:
|
||||
ameba:
|
||||
github: crystal-ameba/ameba
|
||||
version: ~> 1.0.0
|
||||
```
|
||||
|
||||
Build `bin/ameba` binary within your project directory while running `shards install`.
|
||||
|
||||
You may also want to use it on [Travis](travis-ci.org):
|
||||
|
||||
```yaml
|
||||
# .travis.yml
|
||||
language: crystal
|
||||
install:
|
||||
- shards install
|
||||
script:
|
||||
- crystal spec
|
||||
- crystal bin/ameba.cr
|
||||
```
|
||||
|
||||
Using this config Ameba will inspect files just after the specs run. Travis will also fail
|
||||
the build if some problems detected.
|
||||
|
||||
### OS X
|
||||
|
||||
```sh
|
||||
$ brew tap veelenga/tap
|
||||
$ brew tap crystal-ameba/ameba
|
||||
$ brew install ameba
|
||||
```
|
||||
|
||||
|
@ -135,16 +134,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
|
||||
|
||||
|
@ -165,7 +164,7 @@ Generate new file by running `ameba --gen-config`.
|
|||
**List of sources to run Ameba on can be configured globally via:**
|
||||
|
||||
- `Globs` section - an array of wildcards (or paths) to include to the
|
||||
inspection. Defaults to `%w(**/*.cr !lib)`, meaning it includes all project
|
||||
inspection. Defaults to `%w[**/*.cr !lib]`, meaning it includes all project
|
||||
files with `*.cr` extension except those which exist in `lib` folder.
|
||||
- `Excluded` section - an array of wildcards (or paths) to exclude from the
|
||||
source list defined by `Globs`. Defaults to an empty array.
|
||||
|
@ -174,8 +173,8 @@ In this example we define default globs and exclude `src/compiler` folder:
|
|||
|
||||
``` yaml
|
||||
Globs:
|
||||
- **/*.cr
|
||||
- !lib
|
||||
- "**/*.cr"
|
||||
- "!lib"
|
||||
|
||||
Excluded:
|
||||
- src/compiler
|
||||
|
@ -209,19 +208,6 @@ Style/RedundantBegin:
|
|||
Enabled: false
|
||||
```
|
||||
|
||||
### Explain issues
|
||||
|
||||
Ameba allows you to dig deeper into an issue, by showing you details about the issue
|
||||
and the reasoning by it being reported.
|
||||
|
||||
To be convenient, you can just copy-paste the `PATH:line:column` string from the
|
||||
report and paste behind the `ameba` command to check it out.
|
||||
|
||||
```sh
|
||||
$ ameba crystal/command/format.cr:26:83 # show explanation for the issue
|
||||
$ ameba --explain crystal/command/format.cr:26:83 # same thing
|
||||
```
|
||||
|
||||
### Inline disabling
|
||||
|
||||
One or more rules or one or more group of rules can be disabled using inline directives:
|
||||
|
@ -231,18 +217,17 @@ One or more rules or one or more group of rules can be disabled using inline dir
|
|||
time = Time.epoch(1483859302)
|
||||
|
||||
time = Time.epoch(1483859302) # ameba:disable Style/LargeNumbers, Lint/UselessAssign
|
||||
|
||||
time = Time.epoch(1483859302) # ameba:disable Style, Lint
|
||||
```
|
||||
|
||||
## Editors & integrations
|
||||
|
||||
* Vim: [vim-crystal](https://github.com/rhysd/vim-crystal), [Ale](https://github.com/w0rp/ale)
|
||||
* Emacs: [ameba.el](https://github.com/crystal-ameba/ameba.el)
|
||||
* Sublime Text: [Sublime Linter Ameba](https://github.com/epergo/SublimeLinter-contrib-ameba)
|
||||
* VSCode: [vscode-crystal-ameba](https://github.com/crystal-ameba/vscode-crystal-ameba)
|
||||
* Codacy: [codacy-ameba](https://github.com/codacy/codacy-ameba)
|
||||
* GitHub Actions: [github-action](https://github.com/crystal-ameba/github-action)
|
||||
- Vim: [vim-crystal](https://github.com/rhysd/vim-crystal), [Ale](https://github.com/w0rp/ale)
|
||||
- Emacs: [ameba.el](https://github.com/crystal-ameba/ameba.el)
|
||||
- Sublime Text: [Sublime Linter Ameba](https://github.com/epergo/SublimeLinter-contrib-ameba)
|
||||
- VSCode: [vscode-crystal-ameba](https://github.com/crystal-ameba/vscode-crystal-ameba)
|
||||
- Codacy: [codacy-ameba](https://github.com/codacy/codacy-ameba)
|
||||
- GitHub Actions: [github-action](https://github.com/crystal-ameba/github-action)
|
||||
|
||||
## Credits & inspirations
|
||||
|
||||
|
@ -254,3 +239,4 @@ time = Time.epoch(1483859302) # ameba:disable Style, Lint
|
|||
## Contributors
|
||||
|
||||
- [veelenga](https://github.com/veelenga) Vitalii Elenhaupt - creator, maintainer
|
||||
- [Sija](https://github.com/Sija) Sijawusz Pur Rahnama - contributor, maintainer
|
||||
|
|
|
@ -15,7 +15,7 @@ Benchmark.ips do |x|
|
|||
20,
|
||||
30,
|
||||
40,
|
||||
].each do |n|
|
||||
].each do |n| # ameba:disable Naming/BlockParameterName
|
||||
config = Ameba::Config.load
|
||||
config.formatter = Ameba::Formatter::BaseFormatter.new
|
||||
config.globs = get_files(n)
|
||||
|
|
10
shard.yml
10
shard.yml
|
@ -1,20 +1,22 @@
|
|||
name: ameba
|
||||
version: 1.3.1
|
||||
version: 1.6.1
|
||||
|
||||
authors:
|
||||
- Vitalii Elenhaupt <velenhaupt@gmail.com>
|
||||
- Sijawusz Pur Rahnama <sija@sija.pl>
|
||||
|
||||
targets:
|
||||
ameba:
|
||||
main: src/cli.cr
|
||||
|
||||
scripts:
|
||||
# TODO: remove pre-compiled executable in future releases
|
||||
postinstall: make bin && make run_file
|
||||
postinstall: shards build -Dpreview_mt
|
||||
|
||||
# TODO: remove pre-compiled executable in future releases
|
||||
executables:
|
||||
- ameba
|
||||
- ameba.cr
|
||||
|
||||
crystal: "~> 1.6.0"
|
||||
crystal: ~> 1.10
|
||||
|
||||
license: MIT
|
||||
|
|
|
@ -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,36 +275,64 @@ 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
|
||||
end
|
||||
|
||||
context "Crystal::Call" do
|
||||
context "loop" do
|
||||
it "constructs a branch in block" do
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
loop do
|
||||
b = (a = 1)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
branch.to_s.should eq "b = (a = 1)"
|
||||
end
|
||||
end
|
||||
|
||||
context "other" do
|
||||
it "skips constructing a branch in block" do
|
||||
branch = branch_of_assign_in_def <<-CRYSTAL
|
||||
def method(a)
|
||||
1.upto(10) do
|
||||
b = (a = 1)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
branch.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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 +341,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,26 +366,50 @@ 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
|
||||
end
|
||||
|
||||
context "Crystal::Call" do
|
||||
it "returns true if branch is in a loop" do
|
||||
nodes = as_nodes <<-CRYSTAL
|
||||
loop do
|
||||
a = 1
|
||||
end
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.call_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 <<-CRYSTAL
|
||||
1.upto(10) do
|
||||
a = 1
|
||||
end
|
||||
CRYSTAL
|
||||
branchable = Branchable.new nodes.call_nodes.first
|
||||
branch = Branch.new nodes.assign_nodes.first, branchable
|
||||
branch.in_loop?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,7 +7,7 @@ module Ameba::AST
|
|||
node = as_node("return 22")
|
||||
flow_expression = FlowExpression.new node, false
|
||||
flow_expression.node.should_not be_nil
|
||||
flow_expression.in_loop?.should eq false
|
||||
flow_expression.in_loop?.should be_false
|
||||
end
|
||||
|
||||
describe "#delegation" do
|
||||
|
@ -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,47 +49,51 @@ 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
|
||||
)
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
CRYSTAL
|
||||
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
scope.add_variable(var_node)
|
||||
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
|
||||
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.reference nodes.var_nodes.first, scope.inner_scopes.first
|
||||
variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)
|
||||
|
||||
scope.references?(variable).should be_true
|
||||
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
|
||||
)
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
CRYSTAL
|
||||
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
scope.add_variable(var_node)
|
||||
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
|
||||
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.reference nodes.var_nodes.first, scope.inner_scopes.first
|
||||
variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)
|
||||
|
||||
scope.references?(variable, check_inner_scopes: false).should be_false
|
||||
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,10 +101,12 @@ module Ameba::AST
|
|||
3.times { |i| b = b + i }
|
||||
end
|
||||
end
|
||||
)
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
CRYSTAL
|
||||
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
scope.add_variable(var_node)
|
||||
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
|
||||
|
||||
variable = Variable.new(var_node, scope)
|
||||
|
@ -120,7 +126,7 @@ module Ameba::AST
|
|||
describe "#find_variable" do
|
||||
it "returns the variable in the scope by name" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.add_variable Crystal::Var.new "foo"
|
||||
scope.add_variable(Crystal::Var.new "foo")
|
||||
scope.find_variable("foo").should_not be_nil
|
||||
end
|
||||
|
||||
|
@ -133,7 +139,7 @@ module Ameba::AST
|
|||
describe "#assign_variable" do
|
||||
it "creates a new assignment" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.add_variable Crystal::Var.new "foo"
|
||||
scope.add_variable(Crystal::Var.new "foo")
|
||||
scope.assign_variable("foo", Crystal::Var.new "foo")
|
||||
var = scope.find_variable("foo").should_not be_nil
|
||||
var.assignments.size.should eq 1
|
||||
|
@ -141,7 +147,7 @@ module Ameba::AST
|
|||
|
||||
it "does not create the assignment if variable is wrong" do
|
||||
scope = Scope.new as_node("foo = 1")
|
||||
scope.add_variable Crystal::Var.new "foo"
|
||||
scope.add_variable(Crystal::Var.new "foo")
|
||||
scope.assign_variable("bar", Crystal::Var.new "bar")
|
||||
var = scope.find_variable("foo").should_not be_nil
|
||||
var.assignments.size.should eq 0
|
||||
|
@ -150,57 +156,75 @@ 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 "#def?" do
|
||||
context "when check_outer_scopes: true" do
|
||||
it "returns true if outer scope is Crystal::Def" do
|
||||
nodes = as_nodes("def foo; 3.times {}; end")
|
||||
outer_scope = Scope.new nodes.def_nodes.first
|
||||
scope = Scope.new nodes.block_nodes.first, outer_scope
|
||||
scope.def?(check_outer_scopes: true).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
it "returns true if Crystal::Def" do
|
||||
nodes = as_nodes("def foo; end")
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
scope.def?.should be_true
|
||||
end
|
||||
|
||||
it "returns false otherwise" do
|
||||
scope = Scope.new as_node("a = 1")
|
||||
scope.def?.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
|
||||
|
|
|
@ -36,6 +36,43 @@ module Ameba::AST
|
|||
end
|
||||
end
|
||||
|
||||
describe "#static/dynamic_literal?" do
|
||||
[
|
||||
Crystal::ArrayLiteral.new,
|
||||
Crystal::ArrayLiteral.new([Crystal::StringLiteral.new("foo")] of Crystal::ASTNode),
|
||||
Crystal::BoolLiteral.new(false),
|
||||
Crystal::CharLiteral.new('a'),
|
||||
Crystal::HashLiteral.new,
|
||||
Crystal::NamedTupleLiteral.new,
|
||||
Crystal::NilLiteral.new,
|
||||
Crystal::NumberLiteral.new(42),
|
||||
Crystal::RegexLiteral.new(Crystal::StringLiteral.new("")),
|
||||
Crystal::StringLiteral.new("foo"),
|
||||
Crystal::SymbolLiteral.new("foo"),
|
||||
Crystal::TupleLiteral.new([] of Crystal::ASTNode),
|
||||
Crystal::TupleLiteral.new([Crystal::StringLiteral.new("foo")] of Crystal::ASTNode),
|
||||
Crystal::RangeLiteral.new(
|
||||
Crystal::NumberLiteral.new(0),
|
||||
Crystal::NumberLiteral.new(10),
|
||||
true),
|
||||
].each do |literal|
|
||||
it "properly identifies static node #{literal}" do
|
||||
subject.static_literal?(literal).should be_true
|
||||
subject.dynamic_literal?(literal).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
[
|
||||
Crystal::ArrayLiteral.new([Crystal::Path.new(%w[IO])] of Crystal::ASTNode),
|
||||
Crystal::TupleLiteral.new([Crystal::Path.new(%w[IO])] of Crystal::ASTNode),
|
||||
].each do |literal|
|
||||
it "properly identifies dynamic node #{literal}" do
|
||||
subject.dynamic_literal?(literal).should be_true
|
||||
subject.static_literal?(literal).should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#node_source" do
|
||||
it "returns original source of the node" do
|
||||
s = <<-CRYSTAL
|
||||
|
@ -93,7 +130,7 @@ module Ameba::AST
|
|||
|
||||
it "returns false if this is a break out of loop" do
|
||||
node = as_node("break")
|
||||
subject.flow_command?(node, false).should eq false
|
||||
subject.flow_command?(node, false).should be_false
|
||||
end
|
||||
|
||||
it "returns true if this is a next in a loop" do
|
||||
|
@ -103,7 +140,7 @@ module Ameba::AST
|
|||
|
||||
it "returns false if this is a next out of loop" do
|
||||
node = as_node("next")
|
||||
subject.flow_command?(node, false).should eq false
|
||||
subject.flow_command?(node, false).should be_false
|
||||
end
|
||||
|
||||
it "returns true if this is raise" do
|
||||
|
@ -123,7 +160,7 @@ module Ameba::AST
|
|||
|
||||
it "returns false otherwise" do
|
||||
node = as_node("foobar")
|
||||
subject.flow_command?(node, false).should eq false
|
||||
subject.flow_command?(node, false).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -195,7 +232,7 @@ module Ameba::AST
|
|||
break
|
||||
end
|
||||
CRYSTAL
|
||||
subject.flow_expression?(node).should eq false
|
||||
subject.flow_expression?(node).should be_false
|
||||
end
|
||||
|
||||
it "returns true if this until consumed by flow expressions" do
|
||||
|
@ -213,7 +250,7 @@ module Ameba::AST
|
|||
break
|
||||
end
|
||||
CRYSTAL
|
||||
subject.flow_expression?(node).should eq false
|
||||
subject.flow_expression?(node).should be_false
|
||||
end
|
||||
|
||||
it "returns true if this expressions consumed by flow expressions" do
|
||||
|
@ -230,7 +267,7 @@ module Ameba::AST
|
|||
exp1
|
||||
exp2
|
||||
CRYSTAL
|
||||
subject.flow_expression?(node).should eq false
|
||||
subject.flow_expression?(node).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -242,12 +279,12 @@ module Ameba::AST
|
|||
|
||||
it "returns false if it has a receiver" do
|
||||
node = as_node "obj.raise e"
|
||||
subject.raise?(node).should eq false
|
||||
subject.raise?(node).should be_false
|
||||
end
|
||||
|
||||
it "returns false if size of the arguments doesn't match" do
|
||||
node = as_node "raise"
|
||||
subject.raise?(node).should eq false
|
||||
subject.raise?(node).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -264,12 +301,12 @@ module Ameba::AST
|
|||
|
||||
it "returns false if it has a receiver" do
|
||||
node = as_node "obj.exit"
|
||||
subject.exit?(node).should eq false
|
||||
subject.exit?(node).should be_false
|
||||
end
|
||||
|
||||
it "returns false if size of the arguments doesn't match" do
|
||||
node = as_node "exit 1, 1"
|
||||
subject.exit?(node).should eq false
|
||||
subject.exit?(node).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -291,12 +328,12 @@ module Ameba::AST
|
|||
|
||||
it "returns false if it has a receiver" do
|
||||
node = as_node "obj.abort"
|
||||
subject.abort?(node).should eq false
|
||||
subject.abort?(node).should be_false
|
||||
end
|
||||
|
||||
it "returns false if size of the arguments doesn't match" do
|
||||
node = as_node "abort 1, 1, 1"
|
||||
subject.abort?(node).should eq false
|
||||
subject.abort?(node).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -308,12 +345,12 @@ module Ameba::AST
|
|||
|
||||
it "returns false if it has a receiver" do
|
||||
node = as_node "obj.loop"
|
||||
subject.loop?(node).should eq false
|
||||
subject.loop?(node).should be_false
|
||||
end
|
||||
|
||||
it "returns false if size of the arguments doesn't match" do
|
||||
node = as_node "loop 1"
|
||||
subject.loop?(node).should eq false
|
||||
subject.loop?(node).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,44 +74,16 @@ 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)
|
||||
assignment.branch.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#transformed?" do
|
||||
it "returns false if the assignment is not transformed by the compiler" do
|
||||
nodes = as_nodes %(
|
||||
def method(a)
|
||||
a = 2
|
||||
end
|
||||
)
|
||||
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
assignment.transformed?.should be_false
|
||||
end
|
||||
|
||||
it "returns true if the assignment is transformed by the compiler" do
|
||||
nodes = as_nodes %(
|
||||
array.each do |(a, b)|
|
||||
end
|
||||
)
|
||||
|
||||
scope = Scope.new nodes.block_nodes.first
|
||||
variable = Variable.new(nodes.var_nodes.first, scope)
|
||||
assignment = Assignment.new(nodes.assign_nodes.first, variable, scope)
|
||||
assignment.transformed?.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
31
spec/ameba/ast/variabling/type_dec_variable_spec.cr
Normal file
31
spec/ameba/ast/variabling/type_dec_variable_spec.cr
Normal file
|
@ -0,0 +1,31 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
describe TypeDecVariable do
|
||||
var = Crystal::Var.new("foo")
|
||||
declared_type = Crystal::Path.new("String")
|
||||
type_dec = Crystal::TypeDeclaration.new(var, declared_type)
|
||||
|
||||
describe "#initialize" do
|
||||
it "creates a new type dec variable" do
|
||||
variable = TypeDecVariable.new(type_dec)
|
||||
variable.node.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#name" do
|
||||
it "returns var name" do
|
||||
variable = TypeDecVariable.new(type_dec)
|
||||
variable.name.should eq var.name
|
||||
end
|
||||
|
||||
it "raises if type declaration is incorrect" do
|
||||
type_dec = Crystal::TypeDeclaration.new(declared_type, declared_type)
|
||||
|
||||
expect_raises(Exception, "Unsupported var node type: Crystal::Path") do
|
||||
TypeDecVariable.new(type_dec).name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -79,30 +79,35 @@ 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
|
||||
)
|
||||
scope = Scope.new nodes.def_nodes.first
|
||||
CRYSTAL
|
||||
|
||||
var_node = nodes.var_nodes.first
|
||||
scope.add_variable var_node
|
||||
|
||||
scope = Scope.new(nodes.def_nodes.first)
|
||||
scope.add_variable(var_node)
|
||||
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
|
||||
|
||||
variable = Variable.new(var_node, scope)
|
||||
variable.reference nodes.var_nodes.last, scope.inner_scopes.last
|
||||
variable.reference(nodes.var_nodes.last, scope.inner_scopes.last)
|
||||
|
||||
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
|
||||
)
|
||||
scope.add_variable Crystal::Var.new "a"
|
||||
CRYSTAL
|
||||
|
||||
scope.add_variable(Crystal::Var.new "a")
|
||||
variable = scope.variables.first
|
||||
|
||||
variable.captured_by_block?.should be_falsey
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::AST
|
||||
source = Source.new ""
|
||||
|
||||
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,13 +22,13 @@ 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 (
|
||||
loop do
|
||||
|
@ -39,27 +37,27 @@ module Ameba::AST
|
|||
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
|
||||
|
|
|
@ -2,39 +2,50 @@ require "../../../spec_helper"
|
|||
|
||||
module Ameba::AST
|
||||
describe ScopeVisitor do
|
||||
{% for type in %w[class module enum].map(&.id) %}
|
||||
it "creates a scope for the {{ type }} def" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
{{ type }} Foo
|
||||
end
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 1
|
||||
end
|
||||
{% end %}
|
||||
|
||||
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 +53,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
|
||||
|
@ -54,5 +65,33 @@ module Ameba::AST
|
|||
outer_block.outer_scope.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "#visibility" do
|
||||
it "is being properly set" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
private class Foo
|
||||
end
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 1
|
||||
rule.scopes.first.visibility.should eq Crystal::Visibility::Private
|
||||
end
|
||||
|
||||
it "is being inherited from the outer scope(s)" do
|
||||
rule = ScopeRule.new
|
||||
ScopeVisitor.new rule, Source.new <<-CRYSTAL
|
||||
private class Foo
|
||||
class Bar
|
||||
def baz
|
||||
end
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
rule.scopes.size.should eq 3
|
||||
rule.scopes.each &.visibility.should eq Crystal::Visibility::Private
|
||||
rule.scopes.last.node.visibility.should eq Crystal::Visibility::Private
|
||||
rule.scopes[0...-1].each &.node.visibility.should eq Crystal::Visibility::Public
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -10,14 +10,16 @@ module Ameba::Rule
|
|||
end
|
||||
|
||||
it "contains rules across all the available groups" do
|
||||
Rule.rules.map(&.group_name).uniq!.reject!(&.empty?).sort.should eq %w(
|
||||
Rule.rules.map(&.group_name).uniq!.reject!(&.empty?).sort.should eq %w[
|
||||
Ameba
|
||||
Documentation
|
||||
Layout
|
||||
Lint
|
||||
Metrics
|
||||
Naming
|
||||
Performance
|
||||
Style
|
||||
)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,13 +27,17 @@ module Ameba::Rule
|
|||
subject = DummyRule.new
|
||||
|
||||
it "is enabled by default" do
|
||||
subject.enabled.should be_true
|
||||
subject.enabled?.should be_true
|
||||
end
|
||||
|
||||
it "has a description property" do
|
||||
subject.description.should_not be_nil
|
||||
end
|
||||
|
||||
it "has a dummy? property" do
|
||||
subject.dummy?.should be_true
|
||||
end
|
||||
|
||||
it "has excluded property" do
|
||||
subject.excluded.should be_nil
|
||||
end
|
||||
|
@ -44,25 +50,25 @@ module Ameba::Rule
|
|||
|
||||
it "returns false if source is not excluded from this rule" do
|
||||
rule = DummyRule.new
|
||||
rule.excluded = %w(some_source.cr)
|
||||
rule.excluded = %w[some_source.cr]
|
||||
rule.excluded?(Source.new "", "another_source.cr").should_not be_true
|
||||
end
|
||||
|
||||
it "returns true if source is excluded from this rule" do
|
||||
rule = DummyRule.new
|
||||
rule.excluded = %w(source.cr)
|
||||
rule.excluded = %w[source.cr]
|
||||
rule.excluded?(Source.new "", "source.cr").should be_true
|
||||
end
|
||||
|
||||
it "returns true if source matches the wildcard" do
|
||||
rule = DummyRule.new
|
||||
rule.excluded = %w(**/*.cr)
|
||||
rule.excluded = %w[**/*.cr]
|
||||
rule.excluded?(Source.new "", __FILE__).should be_true
|
||||
end
|
||||
|
||||
it "returns false if source does not match the wildcard" do
|
||||
rule = DummyRule.new
|
||||
rule.excluded = %w(*_spec.cr)
|
||||
rule.excluded = %w[*_spec.cr]
|
||||
rule.excluded?(Source.new "", "source.cr").should be_false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,92 +5,97 @@ module Ameba::Cli
|
|||
describe "Cmd" do
|
||||
describe ".run" do
|
||||
it "runs ameba" do
|
||||
r = Cli.run %w(-f silent file.cr)
|
||||
r = Cli.run %w[-f silent file.cr]
|
||||
r.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe ".parse_args" do
|
||||
%w(-s --silent).each do |f|
|
||||
it "accepts #{f} flag" do
|
||||
c = Cli.parse_args [f]
|
||||
%w[-s --silent].each do |flag|
|
||||
it "accepts #{flag} flag" do
|
||||
c = Cli.parse_args [flag]
|
||||
c.formatter.should eq :silent
|
||||
end
|
||||
end
|
||||
|
||||
%w(-c --config).each do |f|
|
||||
it "accepts #{f} flag" do
|
||||
c = Cli.parse_args [f, "config.yml"]
|
||||
c.config.should eq "config.yml"
|
||||
%w[-c --config].each do |flag|
|
||||
it "accepts #{flag} flag" do
|
||||
c = Cli.parse_args [flag, "config.yml"]
|
||||
c.config.should eq Path["config.yml"]
|
||||
end
|
||||
end
|
||||
|
||||
%w(-f --format).each do |f|
|
||||
it "accepts #{f} flag" do
|
||||
c = Cli.parse_args [f, "my-formatter"]
|
||||
%w[-f --format].each do |flag|
|
||||
it "accepts #{flag} flag" do
|
||||
c = Cli.parse_args [flag, "my-formatter"]
|
||||
c.formatter.should eq "my-formatter"
|
||||
end
|
||||
end
|
||||
|
||||
it "accepts --only flag" do
|
||||
c = Cli.parse_args ["--only", "RULE1,RULE2"]
|
||||
c.only.should eq %w(RULE1 RULE2)
|
||||
c.only.should eq %w[RULE1 RULE2]
|
||||
end
|
||||
|
||||
it "accepts --except flag" do
|
||||
c = Cli.parse_args ["--except", "RULE1,RULE2"]
|
||||
c.except.should eq %w(RULE1 RULE2)
|
||||
c.except.should eq %w[RULE1 RULE2]
|
||||
end
|
||||
|
||||
it "defaults rules? flag to false" do
|
||||
c = Cli.parse_args %w(file.cr)
|
||||
c.rules?.should eq false
|
||||
c = Cli.parse_args %w[file.cr]
|
||||
c.rules?.should be_false
|
||||
end
|
||||
|
||||
it "defaults skip_reading_config? flag to false" do
|
||||
c = Cli.parse_args %w[file.cr]
|
||||
c.skip_reading_config?.should be_false
|
||||
end
|
||||
|
||||
it "accepts --rules flag" do
|
||||
c = Cli.parse_args %w(--rules)
|
||||
c = Cli.parse_args %w[--rules]
|
||||
c.rules?.should eq true
|
||||
end
|
||||
|
||||
it "defaults all? flag to false" do
|
||||
c = Cli.parse_args %w(file.cr)
|
||||
c.all?.should eq false
|
||||
c = Cli.parse_args %w[file.cr]
|
||||
c.all?.should be_false
|
||||
end
|
||||
|
||||
it "accepts --all flag" do
|
||||
c = Cli.parse_args %w(--all)
|
||||
c = Cli.parse_args %w[--all]
|
||||
c.all?.should eq true
|
||||
end
|
||||
|
||||
it "accepts --gen-config flag" do
|
||||
c = Cli.parse_args %w(--gen-config)
|
||||
c = Cli.parse_args %w[--gen-config]
|
||||
c.formatter.should eq :todo
|
||||
end
|
||||
|
||||
it "accepts --no-color flag" do
|
||||
c = Cli.parse_args %w(--no-color)
|
||||
c = Cli.parse_args %w[--no-color]
|
||||
c.colors?.should be_false
|
||||
end
|
||||
|
||||
it "accepts --without-affected-code flag" do
|
||||
c = Cli.parse_args %w(--without-affected-code)
|
||||
c = Cli.parse_args %w[--without-affected-code]
|
||||
c.without_affected_code?.should be_true
|
||||
end
|
||||
|
||||
it "doesn't disable colors by default" do
|
||||
c = Cli.parse_args %w(--all)
|
||||
c = Cli.parse_args %w[--all]
|
||||
c.colors?.should be_true
|
||||
end
|
||||
|
||||
it "ignores --config if --gen-config flag passed" do
|
||||
c = Cli.parse_args %w(--gen-config --config my_config.yml)
|
||||
c = Cli.parse_args %w[--gen-config --config my_config.yml]
|
||||
c.formatter.should eq :todo
|
||||
c.config.should eq ""
|
||||
c.skip_reading_config?.should be_true
|
||||
end
|
||||
|
||||
describe "-e/--explain" do
|
||||
it "configures file/line/column" do
|
||||
c = Cli.parse_args %w(--explain src/file.cr:3:5)
|
||||
c = Cli.parse_args %w[--explain src/file.cr:3:5]
|
||||
|
||||
location_to_explain = c.location_to_explain.should_not be_nil
|
||||
location_to_explain[:file].should eq "src/file.cr"
|
||||
|
@ -100,59 +105,59 @@ module Ameba::Cli
|
|||
|
||||
it "raises an error if location is not valid" do
|
||||
expect_raises(Exception, "location should have PATH:line:column") do
|
||||
Cli.parse_args %w(--explain src/file.cr:3)
|
||||
Cli.parse_args %w[--explain src/file.cr:3]
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error if line number is not valid" do
|
||||
expect_raises(Exception, "location should have PATH:line:column") do
|
||||
Cli.parse_args %w(--explain src/file.cr:a:3)
|
||||
Cli.parse_args %w[--explain src/file.cr:a:3]
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error if column number is not valid" do
|
||||
expect_raises(Exception, "location should have PATH:line:column") do
|
||||
Cli.parse_args %w(--explain src/file.cr:3:&)
|
||||
Cli.parse_args %w[--explain src/file.cr:3:&]
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error if line/column are missing" do
|
||||
expect_raises(Exception, "location should have PATH:line:column") do
|
||||
Cli.parse_args %w(--explain src/file.cr)
|
||||
Cli.parse_args %w[--explain src/file.cr]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "--fail-level" do
|
||||
it "configures fail level Convention" do
|
||||
c = Cli.parse_args %w(--fail-level convention)
|
||||
c = Cli.parse_args %w[--fail-level convention]
|
||||
c.fail_level.should eq Severity::Convention
|
||||
end
|
||||
|
||||
it "configures fail level Warning" do
|
||||
c = Cli.parse_args %w(--fail-level Warning)
|
||||
c = Cli.parse_args %w[--fail-level Warning]
|
||||
c.fail_level.should eq Severity::Warning
|
||||
end
|
||||
|
||||
it "configures fail level Error" do
|
||||
c = Cli.parse_args %w(--fail-level error)
|
||||
c = Cli.parse_args %w[--fail-level error]
|
||||
c.fail_level.should eq Severity::Error
|
||||
end
|
||||
|
||||
it "raises if fail level is incorrect" do
|
||||
expect_raises(Exception, "Incorrect severity name JohnDoe") do
|
||||
Cli.parse_args %w(--fail-level JohnDoe)
|
||||
Cli.parse_args %w[--fail-level JohnDoe]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "accepts unknown args as globs" do
|
||||
c = Cli.parse_args %w(source1.cr source2.cr)
|
||||
c.globs.should eq %w(source1.cr source2.cr)
|
||||
c = Cli.parse_args %w[source1.cr source2.cr]
|
||||
c.globs.should eq %w[source1.cr source2.cr]
|
||||
end
|
||||
|
||||
it "accepts one unknown arg as explain location if it has correct format" do
|
||||
c = Cli.parse_args %w(source.cr:3:22)
|
||||
c = Cli.parse_args %w[source.cr:3:22]
|
||||
|
||||
location_to_explain = c.location_to_explain.should_not be_nil
|
||||
location_to_explain[:file].should eq "source.cr"
|
||||
|
@ -166,7 +171,7 @@ module Ameba::Cli
|
|||
c.globs.should be_nil
|
||||
c.only.should be_nil
|
||||
c.except.should be_nil
|
||||
c.config.should eq Config::PATH
|
||||
c.config.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ require "../spec_helper"
|
|||
|
||||
module Ameba
|
||||
describe Config do
|
||||
config_sample = "config/ameba.yml"
|
||||
config_sample = "spec/fixtures/config.yml"
|
||||
|
||||
it "should have a list of available formatters" do
|
||||
Config::AVAILABLE_FORMATTERS.should_not be_nil
|
||||
|
@ -21,7 +21,7 @@ module Ameba
|
|||
Globs: src/*.cr
|
||||
CONFIG
|
||||
config = Config.new(yml)
|
||||
config.globs.should eq %w(src/*.cr)
|
||||
config.globs.should eq %w[src/*.cr]
|
||||
end
|
||||
|
||||
it "initializes globs as array" do
|
||||
|
@ -32,7 +32,7 @@ module Ameba
|
|||
- "!spec"
|
||||
CONFIG
|
||||
config = Config.new(yml)
|
||||
config.globs.should eq %w(src/*.cr !spec)
|
||||
config.globs.should eq %w[src/*.cr !spec]
|
||||
end
|
||||
|
||||
it "raises if Globs has a wrong type" do
|
||||
|
@ -51,7 +51,7 @@ module Ameba
|
|||
Excluded: spec
|
||||
CONFIG
|
||||
config = Config.new(yml)
|
||||
config.excluded.should eq %w(spec)
|
||||
config.excluded.should eq %w[spec]
|
||||
end
|
||||
|
||||
it "initializes excluded as array" do
|
||||
|
@ -62,7 +62,7 @@ module Ameba
|
|||
- lib/*.cr
|
||||
CONFIG
|
||||
config = Config.new(yml)
|
||||
config.excluded.should eq %w(spec lib/*.cr)
|
||||
config.excluded.should eq %w[spec lib/*.cr]
|
||||
end
|
||||
|
||||
it "raises if Excluded has a wrong type" do
|
||||
|
@ -84,6 +84,12 @@ module Ameba
|
|||
config.formatter.should_not be_nil
|
||||
end
|
||||
|
||||
it "raises when custom config file doesn't exist" do
|
||||
expect_raises(Exception, "Unable to load config file: Config file does not exist") do
|
||||
Config.load "foo.yml"
|
||||
end
|
||||
end
|
||||
|
||||
it "loads default config" do
|
||||
config = Config.load
|
||||
config.should_not be_nil
|
||||
|
@ -127,13 +133,13 @@ module Ameba
|
|||
config.sources.any?(&.fullpath.==(__FILE__)).should be_true
|
||||
end
|
||||
|
||||
it "returns a list of sources mathing globs" do
|
||||
config.globs = %w(**/config_spec.cr)
|
||||
it "returns a list of sources matching globs" do
|
||||
config.globs = %w[**/config_spec.cr]
|
||||
config.sources.size.should eq(1)
|
||||
end
|
||||
|
||||
it "returns a lisf of sources excluding 'Excluded'" do
|
||||
config.excluded = %w(**/config_spec.cr)
|
||||
it "returns a list of sources excluding 'Excluded'" do
|
||||
config.excluded = %w[**/config_spec.cr]
|
||||
config.sources.any?(&.fullpath.==(__FILE__)).should be_false
|
||||
end
|
||||
end
|
||||
|
@ -170,12 +176,12 @@ module Ameba
|
|||
name = DummyRule.rule_name
|
||||
config.update_rule name, enabled: false
|
||||
rule = config.rules.find!(&.name.== name)
|
||||
rule.enabled.should be_false
|
||||
rule.enabled?.should be_false
|
||||
end
|
||||
|
||||
it "updates excluded property" do
|
||||
name = DummyRule.rule_name
|
||||
excluded = %w(spec/source.cr)
|
||||
excluded = %w[spec/source.cr]
|
||||
config.update_rule name, excluded: excluded
|
||||
rule = config.rules.find!(&.name.== name)
|
||||
rule.excluded.should eq excluded
|
||||
|
@ -189,12 +195,12 @@ module Ameba
|
|||
name = DummyRule.rule_name
|
||||
config.update_rules [name], enabled: false
|
||||
rule = config.rules.find!(&.name.== name)
|
||||
rule.enabled.should be_false
|
||||
rule.enabled?.should be_false
|
||||
end
|
||||
|
||||
it "updates multiple rules by excluded property" do
|
||||
name = DummyRule.rule_name
|
||||
excluded = %w(spec/source.cr)
|
||||
excluded = %w[spec/source.cr]
|
||||
config.update_rules [name], excluded: excluded
|
||||
rule = config.rules.find!(&.name.== name)
|
||||
rule.excluded.should eq excluded
|
||||
|
@ -204,12 +210,12 @@ module Ameba
|
|||
group = DummyRule.group_name
|
||||
config.update_rules [group], enabled: false
|
||||
rule = config.rules.find!(&.name.== DummyRule.rule_name)
|
||||
rule.enabled.should be_false
|
||||
rule.enabled?.should be_false
|
||||
end
|
||||
|
||||
it "updates a group by excluded property" do
|
||||
name = DummyRule.group_name
|
||||
excluded = %w(spec/source.cr)
|
||||
excluded = %w[spec/source.cr]
|
||||
config.update_rules [name], excluded: excluded
|
||||
rule = config.rules.find!(&.name.== DummyRule.rule_name)
|
||||
rule.excluded.should eq excluded
|
||||
|
|
|
@ -59,7 +59,7 @@ module Ameba
|
|||
source = Source.new "a = 42", "source.cr"
|
||||
output = explanation(source)
|
||||
output.should contain "DETAILED DESCRIPTION"
|
||||
output.should contain "TO BE DONE..."
|
||||
output.should contain "Rule extended description"
|
||||
end
|
||||
|
||||
it "writes nothing if location not found" do
|
||||
|
|
|
@ -1,43 +1,54 @@
|
|||
require "../../spec_helper"
|
||||
require "file_utils"
|
||||
|
||||
CONFIG_PATH = Path[Dir.tempdir] / Ameba::Config::FILENAME
|
||||
|
||||
module Ameba
|
||||
private def with_formatter(&)
|
||||
io = IO::Memory.new
|
||||
formatter = Formatter::TODOFormatter.new(io, CONFIG_PATH)
|
||||
|
||||
yield formatter, io
|
||||
end
|
||||
|
||||
private def create_todo
|
||||
formatter = Formatter::TODOFormatter.new IO::Memory.new
|
||||
with_formatter do |formatter|
|
||||
s = Source.new "a = 1", "source.cr"
|
||||
s.add_issue DummyRule.new, {1, 2}, "message"
|
||||
file = formatter.finished([s])
|
||||
file ? File.read(file.path) : ""
|
||||
end
|
||||
end
|
||||
|
||||
describe Formatter::TODOFormatter do
|
||||
::Spec.after_each do
|
||||
FileUtils.rm(Ameba::Config::PATH) if File.exists?(Ameba::Config::PATH)
|
||||
FileUtils.rm_rf(CONFIG_PATH)
|
||||
end
|
||||
|
||||
context "problems not found" do
|
||||
it "does not create file" do
|
||||
formatter = Formatter::TODOFormatter.new IO::Memory.new
|
||||
with_formatter do |formatter|
|
||||
file = formatter.finished [Source.new ""]
|
||||
file.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "reports a message saying file is not created" do
|
||||
io = IO::Memory.new
|
||||
formatter = Formatter::TODOFormatter.new io
|
||||
with_formatter do |formatter, io|
|
||||
formatter.finished [Source.new ""]
|
||||
io.to_s.should contain "No issues found. File is not generated"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "problems found" do
|
||||
it "prints a message saying file is created" do
|
||||
io = IO::Memory.new
|
||||
formatter = Formatter::TODOFormatter.new io
|
||||
with_formatter do |formatter, io|
|
||||
s = Source.new "a = 1", "source.cr"
|
||||
s.add_issue DummyRule.new, {1, 2}, "message"
|
||||
formatter.finished([s])
|
||||
io.to_s.should contain "Created .ameba.yml"
|
||||
io.to_s.should contain "Created #{CONFIG_PATH}"
|
||||
end
|
||||
end
|
||||
|
||||
it "creates a valid YAML document" do
|
||||
|
@ -77,8 +88,8 @@ module Ameba
|
|||
end
|
||||
|
||||
context "with multiple issues" do
|
||||
formatter = Formatter::TODOFormatter.new IO::Memory.new
|
||||
|
||||
it "does generate todo file" do
|
||||
with_formatter do |formatter|
|
||||
s1 = Source.new "a = 1", "source1.cr"
|
||||
s2 = Source.new "a = 1", "source2.cr"
|
||||
s1.add_issue DummyRule.new, {1, 2}, "message1"
|
||||
|
@ -93,6 +104,7 @@ module Ameba
|
|||
# Run `ameba --only Ameba/DummyRule` for details
|
||||
Ameba/DummyRule:
|
||||
Description: Dummy rule that does nothing.
|
||||
Dummy: true
|
||||
Excluded:
|
||||
- source1.cr
|
||||
- source2.cr
|
||||
|
@ -100,20 +112,22 @@ module Ameba
|
|||
Severity: Convention
|
||||
CONTENT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when invalid syntax" do
|
||||
it "does generate todo file" do
|
||||
formatter = Formatter::TODOFormatter.new IO::Memory.new
|
||||
with_formatter do |formatter|
|
||||
s = Source.new "def invalid_syntax"
|
||||
s.add_issue Rule::Lint::Syntax.new, {1, 2}, "message"
|
||||
|
||||
file = formatter.finished [s]
|
||||
file.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "prints an error message" do
|
||||
io = IO::Memory.new
|
||||
formatter = Formatter::TODOFormatter.new io
|
||||
with_formatter do |formatter, io|
|
||||
s = Source.new "def invalid_syntax"
|
||||
s.add_issue Rule::Lint::Syntax.new, {1, 2}, "message"
|
||||
|
||||
|
@ -125,3 +139,4 @@ module Ameba
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,7 +39,7 @@ module Ameba::Formatter
|
|||
|
||||
describe "#context" do
|
||||
it "returns correct pre/post context lines" do
|
||||
source = Source.new <<-EOF
|
||||
source = Source.new <<-CRYSTAL
|
||||
# pre:1
|
||||
# pre:2
|
||||
# pre:3
|
||||
|
@ -51,7 +51,7 @@ module Ameba::Formatter
|
|||
# post:3
|
||||
# post:4
|
||||
# post:5
|
||||
EOF
|
||||
CRYSTAL
|
||||
|
||||
subject.context(source.lines, lineno: 6, context_lines: 3)
|
||||
.should eq({<<-PRE.lines, <<-POST.lines
|
||||
|
@ -69,24 +69,24 @@ module Ameba::Formatter
|
|||
|
||||
describe "#affected_code" do
|
||||
it "returns nil if there is no such a line number" do
|
||||
code = <<-EOF
|
||||
code = <<-CRYSTAL
|
||||
a = 1
|
||||
EOF
|
||||
CRYSTAL
|
||||
location = Crystal::Location.new("filename", 2, 1)
|
||||
subject.affected_code(code, location).should be_nil
|
||||
end
|
||||
|
||||
it "returns correct line if it is found" do
|
||||
code = <<-EOF
|
||||
code = <<-CRYSTAL
|
||||
a = 1
|
||||
EOF
|
||||
CRYSTAL
|
||||
location = Crystal::Location.new("filename", 1, 1)
|
||||
subject.deansify(subject.affected_code(code, location))
|
||||
.should eq "> a = 1\n ^\n"
|
||||
end
|
||||
|
||||
it "returns correct line if it is found" do
|
||||
code = <<-EOF
|
||||
code = <<-CRYSTAL
|
||||
# pre:1
|
||||
# pre:2
|
||||
# pre:3
|
||||
|
@ -98,7 +98,7 @@ module Ameba::Formatter
|
|||
# post:3
|
||||
# post:4
|
||||
# post:5
|
||||
EOF
|
||||
CRYSTAL
|
||||
|
||||
location = Crystal::Location.new("filename", 6, 1)
|
||||
subject.deansify(subject.affected_code(code, location, context_lines: 3))
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
require "../spec_helper"
|
||||
|
||||
module Ameba
|
||||
struct GlobUtilsClass
|
||||
include GlobUtils
|
||||
end
|
||||
|
||||
subject = GlobUtilsClass.new
|
||||
subject = GlobUtils
|
||||
current_file_basename = File.basename(__FILE__)
|
||||
current_file_path = "spec/ameba/#{current_file_basename}"
|
||||
|
||||
|
@ -45,6 +41,12 @@ module Ameba
|
|||
subject.expand(["**/#{current_file_basename}", "**/#{current_file_basename}"])
|
||||
.should eq [current_file_path]
|
||||
end
|
||||
|
||||
it "does not list folders" do
|
||||
subject.expand(["**/*"]).each do |path|
|
||||
fail "#{path.inspect} should be a file" unless File.file?(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
32
spec/ameba/presenter/rule_collection_presenter_spec.cr
Normal file
32
spec/ameba/presenter/rule_collection_presenter_spec.cr
Normal file
|
@ -0,0 +1,32 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
private def with_rule_collection_presenter(&)
|
||||
with_presenter(Presenter::RuleCollectionPresenter) do |presenter, io|
|
||||
rules = Config.load.rules
|
||||
presenter.run(rules)
|
||||
|
||||
output = io.to_s
|
||||
output = Formatter::Util.deansify(output).to_s
|
||||
|
||||
yield rules, output, presenter
|
||||
end
|
||||
end
|
||||
|
||||
describe Presenter::RuleCollectionPresenter do
|
||||
it "outputs rule collection details" do
|
||||
with_rule_collection_presenter do |rules, output|
|
||||
rules.each do |rule|
|
||||
output.should contain rule.name
|
||||
output.should contain rule.severity.symbol
|
||||
|
||||
if description = rule.description
|
||||
output.should contain description
|
||||
end
|
||||
end
|
||||
output.should contain "Total rules: #{rules.size}"
|
||||
output.should match /\d+ enabled/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
30
spec/ameba/presenter/rule_presenter_spec.cr
Normal file
30
spec/ameba/presenter/rule_presenter_spec.cr
Normal file
|
@ -0,0 +1,30 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
private def rule_presenter_each_rule(&)
|
||||
with_presenter(Presenter::RulePresenter) do |presenter, io|
|
||||
rules = Config.load.rules
|
||||
rules.each do |rule|
|
||||
presenter.run(rule)
|
||||
|
||||
output = io.to_s
|
||||
output = Formatter::Util.deansify(output).to_s
|
||||
|
||||
yield rule, output, presenter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Presenter::RulePresenter do
|
||||
it "outputs rule details" do
|
||||
rule_presenter_each_rule do |rule, output|
|
||||
output.should contain rule.name
|
||||
output.should contain rule.severity.to_s
|
||||
|
||||
if description = rule.description
|
||||
output.should contain description
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
113
spec/ameba/rule/documentation/documentation_admonition_spec.cr
Normal file
113
spec/ameba/rule/documentation/documentation_admonition_spec.cr
Normal file
|
@ -0,0 +1,113 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Documentation
|
||||
subject = DocumentationAdmonition.new
|
||||
|
||||
describe DocumentationAdmonition do
|
||||
it "passes for comments with admonition mid-word/sentence" do
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
# Mentioning #{admonition} mid-sentence
|
||||
# x#{admonition}x
|
||||
# x#{admonition}
|
||||
# #{admonition}x
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for comments with admonition" do
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}: Single-line comment
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
CRYSTAL
|
||||
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# Text before ...
|
||||
# #{admonition}(some context): Part of multi-line comment
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
# Text after ...
|
||||
CRYSTAL
|
||||
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
if rand > 0.5
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "with date" do
|
||||
it "passes for admonitions with future date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
future_date = (Time.utc + 21.days).to_s(format: "%F")
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
# #{admonition}(#{future_date}): sth in the future
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with past date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
past_date = (Time.utc - 21.days).to_s(format: "%F")
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(#{past_date}): sth in the past
|
||||
# ^{} error: Found a #{admonition} admonition in a comment (21 days past)
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with yesterday's date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
yesterday_date = (Time.utc - 1.day).to_s(format: "%F")
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(#{yesterday_date}): sth in the past
|
||||
# ^{} error: Found a #{admonition} admonition in a comment (1 day past)
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with today's date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
today_date = Time.utc.to_s(format: "%F")
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(#{today_date}): sth in the past
|
||||
# ^{} error: Found a #{admonition} admonition in a comment (today is the day!)
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with invalid date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(0000-00-00): sth wrong
|
||||
# ^{} error: #{admonition} admonition error: Invalid time: "0000-00-00"
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
describe "#admonitions" do
|
||||
it "lets setting custom admonitions" do
|
||||
rule = DocumentationAdmonition.new
|
||||
rule.admonitions = %w[FOO BAR]
|
||||
|
||||
rule.admonitions.each do |admonition|
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
# #{admonition}
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
# #{admonition}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
151
spec/ameba/rule/documentation/documentation_spec.cr
Normal file
151
spec/ameba/rule/documentation/documentation_spec.cr
Normal file
|
@ -0,0 +1,151 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Documentation
|
||||
subject = Documentation.new
|
||||
.tap(&.ignore_classes = false)
|
||||
.tap(&.ignore_modules = false)
|
||||
.tap(&.ignore_enums = false)
|
||||
.tap(&.ignore_defs = false)
|
||||
.tap(&.ignore_macros = false)
|
||||
|
||||
describe Documentation do
|
||||
it "passes for undocumented private types" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
private class Foo
|
||||
def foo
|
||||
end
|
||||
end
|
||||
|
||||
private module Bar
|
||||
def bar
|
||||
end
|
||||
end
|
||||
|
||||
private enum Baz
|
||||
end
|
||||
|
||||
private def bat
|
||||
end
|
||||
|
||||
private macro bag
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "passes for documented public types" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
# Foo
|
||||
class Foo
|
||||
# foo
|
||||
def foo
|
||||
end
|
||||
end
|
||||
|
||||
# Bar
|
||||
module Bar
|
||||
# bar
|
||||
def bar
|
||||
end
|
||||
end
|
||||
|
||||
# Baz
|
||||
enum Baz
|
||||
end
|
||||
|
||||
# bat
|
||||
def bat
|
||||
end
|
||||
|
||||
# bag
|
||||
macro bag
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "fails if there is an undocumented public type" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
class Foo
|
||||
# ^^^^^^^^^ error: Missing documentation
|
||||
end
|
||||
|
||||
module Bar
|
||||
# ^^^^^^^^^^ error: Missing documentation
|
||||
end
|
||||
|
||||
enum Baz
|
||||
# ^^^^^^^^ error: Missing documentation
|
||||
end
|
||||
|
||||
def bat
|
||||
# ^^^^^^^ error: Missing documentation
|
||||
end
|
||||
|
||||
macro bag
|
||||
# ^^^^^^^^^ error: Missing documentation
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
describe "#ignore_classes" do
|
||||
it "lets the rule to ignore method definitions if true" do
|
||||
rule = Documentation.new
|
||||
rule.ignore_classes = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
class Foo
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ignore_modules" do
|
||||
it "lets the rule to ignore method definitions if true" do
|
||||
rule = Documentation.new
|
||||
rule.ignore_modules = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
module Bar
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ignore_enums" do
|
||||
it "lets the rule to ignore method definitions if true" do
|
||||
rule = Documentation.new
|
||||
rule.ignore_enums = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
enum Baz
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ignore_defs" do
|
||||
it "lets the rule to ignore method definitions if true" do
|
||||
rule = Documentation.new
|
||||
rule.ignore_defs = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
def bat
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ignore_macros" do
|
||||
it "lets the rule to ignore macros if true" do
|
||||
rule = Documentation.new
|
||||
rule.ignore_macros = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
macro bag
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
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
|
||||
s = Source.new code
|
||||
private def it_detects_empty_expression(code, *, file = __FILE__, line = __LINE__)
|
||||
it "detects empty expression #{code.inspect}", file, line do
|
||||
source = Source.new code
|
||||
rule = Rule::Lint::EmptyExpression.new
|
||||
rule.catch(s).should_not be_valid
|
||||
rule.catch(source).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 %(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method()
|
||||
end
|
||||
|
||||
|
@ -30,8 +30,7 @@ module Ameba
|
|||
|
||||
begin "" end
|
||||
[nil] << nil
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it_detects_empty_expression %(())
|
||||
|
@ -84,11 +83,6 @@ module Ameba
|
|||
it_detects_empty_expression %(
|
||||
begin; end
|
||||
)
|
||||
it_detects_empty_expression %(
|
||||
begin
|
||||
nil
|
||||
end
|
||||
)
|
||||
it_detects_empty_expression %(
|
||||
begin
|
||||
()
|
||||
|
@ -96,10 +90,10 @@ module Ameba
|
|||
)
|
||||
|
||||
it "does not report empty expression in macro" do
|
||||
s = Source.new %q(
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
module MyModule
|
||||
macro conditional_error_for_inline_callbacks
|
||||
\{%
|
||||
\\{%
|
||||
raise ""
|
||||
%}
|
||||
end
|
||||
|
@ -107,21 +101,7 @@ module Ameba
|
|||
macro before_save(x = nil)
|
||||
end
|
||||
end
|
||||
)
|
||||
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"
|
||||
CRYSTAL
|
||||
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
|
||||
|
|
48
spec/ameba/rule/lint/formatting_spec.cr
Normal file
48
spec/ameba/rule/lint/formatting_spec.cr
Normal file
|
@ -0,0 +1,48 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
describe Formatting do
|
||||
subject = Formatting.new
|
||||
|
||||
it "passes if source is formatted" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b)
|
||||
a + b
|
||||
end
|
||||
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if source is not formatted" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def method(a,b)
|
||||
# ^{} error: Use built-in formatter to format this source
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
context "#fail_on_error" do
|
||||
it "passes on formatter errors by default" do
|
||||
rule = Formatting.new
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
def method(a, b)
|
||||
a + b
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports on formatter errors when enabled" do
|
||||
rule = Formatting.new
|
||||
rule.fail_on_error = true
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
def method(a, b)
|
||||
a + b
|
||||
# ^ error: Error while formatting: expecting identifier 'end', not 'EOF'
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
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
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
LITERAL_SAMPLES = {
|
||||
nil, true, 42, 4.2, 'c', "foo", :foo, /foo/,
|
||||
0..42, [1, 2, 3], {1, 2, 3},
|
||||
{foo: :bar}, {:foo => :bar},
|
||||
}
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
subject = LiteralAssignmentsInExpressions.new
|
||||
|
||||
describe LiteralAssignmentsInExpressions do
|
||||
it "passes if the assignment value is not a literal" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
if a = b
|
||||
:ok
|
||||
end
|
||||
|
||||
unless a = b.presence
|
||||
:ok
|
||||
end
|
||||
|
||||
:ok if a = b
|
||||
:ok unless a = b
|
||||
|
||||
case {a, b}
|
||||
when {0, 1} then :gt
|
||||
when {1, 0} then :lt
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
{% for literal in LITERAL_SAMPLES %}
|
||||
it %(reports if the assignment value is a {{ literal }} literal) do
|
||||
expect_issue subject, <<-CRYSTAL, literal: {{ literal.stringify }}
|
||||
raise "boo!" if foo = {{ literal }}
|
||||
# ^{literal}^^^^^^ error: Detected assignment with a literal value in control expression
|
||||
CRYSTAL
|
||||
|
||||
expect_issue subject, <<-CRYSTAL, literal: {{ literal.stringify }}
|
||||
raise "boo!" unless foo = {{ literal }}
|
||||
# ^{literal}^^^^^^ error: Detected assignment with a literal value in control expression
|
||||
CRYSTAL
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
end
|
|
@ -6,16 +6,20 @@ module Ameba::Rule::Lint
|
|||
describe LiteralsComparison do
|
||||
it "passes for valid cases" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
{start.year, start.month} == {stop.year, stop.month}
|
||||
["foo"] === [foo]
|
||||
"foo" == foo
|
||||
"foo" != foo
|
||||
"foo" == FOO
|
||||
FOO == "foo"
|
||||
foo == "foo"
|
||||
foo != "foo"
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a regex comparison possibly evaluating to the same" do
|
||||
it "reports if there is a dynamic comparison possibly evaluating to the same" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
/foo/ === "foo"
|
||||
[foo] === [foo]
|
||||
# ^^^^^^^^^^^^^ error: Comparison most likely evaluates to the same
|
||||
CRYSTAL
|
||||
end
|
||||
|
@ -41,39 +45,22 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a static path comparison evaluating to false" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
String == Nil
|
||||
# ^^^^^^^^^^^ error: Comparison always evaluates to false
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "macro" do
|
||||
pending "reports in macro scope" do
|
||||
it "reports in macro scope" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
{{ "foo" == "foo" }}
|
||||
# ^^^^^^^^^^^^^^ error: Comparison always evaluates to true
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "passes for free variables comparisons in macro scope" do
|
||||
it "passes for valid cases" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
{{ T == Nil }}
|
||||
{{ "foo" == foo }}
|
||||
{{ "foo" != foo }}
|
||||
{% foo == "foo" %}
|
||||
{% foo != "foo" %}
|
||||
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
|
||||
|
|
42
spec/ameba/rule/lint/missing_block_argument_spec.cr
Normal file
42
spec/ameba/rule/lint/missing_block_argument_spec.cr
Normal file
|
@ -0,0 +1,42 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
subject = MissingBlockArgument.new
|
||||
|
||||
describe MissingBlockArgument do
|
||||
it "passes if the block argument is defined" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def foo(&)
|
||||
yield 42
|
||||
end
|
||||
|
||||
def bar(&block)
|
||||
yield 24
|
||||
end
|
||||
|
||||
def baz(a, b, c, &block)
|
||||
yield a, b, c
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if the block argument is missing" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def foo
|
||||
# ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method.
|
||||
yield 42
|
||||
end
|
||||
|
||||
def bar
|
||||
# ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method.
|
||||
yield 24
|
||||
end
|
||||
|
||||
def baz(a, b, c)
|
||||
# ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method.
|
||||
yield a, b, c
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,8 +7,11 @@ module Ameba::Rule::Lint
|
|||
it "passes for valid cases" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
(1..3).index(1).not_nil!(:foo)
|
||||
(1..3).rindex(1).not_nil!(:foo)
|
||||
(1..3).index { |i| i > 2 }.not_nil!(:foo)
|
||||
(1..3).rindex { |i| i > 2 }.not_nil!(:foo)
|
||||
(1..3).find { |i| i > 2 }.not_nil!(:foo)
|
||||
/(.)(.)(.)/.match("abc", &.itself).not_nil!
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
|
@ -23,6 +26,28 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is an `rindex` call followed by `not_nil!`" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
(1..3).rindex(1).not_nil!
|
||||
# ^^^^^^^^^^^^^^^^^^ error: Use `rindex! {...}` instead of `rindex {...}.not_nil!`
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
(1..3).rindex!(1)
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is an `match` call followed by `not_nil!`" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
/(.)(.)(.)/.match("abc").not_nil![2]
|
||||
# ^^^^^^^^^^^^^^^^^^^^^ error: Use `match! {...}` instead of `match {...}.not_nil!`
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
/(.)(.)(.)/.match!("abc")[2]
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is an `index` call with block followed by `not_nil!`" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
(1..3).index { |i| i > 2 }.not_nil!
|
||||
|
@ -34,6 +59,17 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is an `rindex` call with block followed by `not_nil!`" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
(1..3).rindex { |i| i > 2 }.not_nil!
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `rindex! {...}` instead of `rindex {...}.not_nil!`
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
(1..3).rindex! { |i| i > 2 }
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a `find` call with block followed by `not_nil!`" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
(1..3).find { |i| i > 2 }.not_nil!
|
||||
|
@ -58,18 +94,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
|
||||
|
|
|
@ -18,6 +18,13 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a `not_nil!` call in the middle of the call-chain" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
(1..3).first?.not_nil!.to_s
|
||||
# ^^^^^^^^ error: Avoid using `not_nil!`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "macro" do
|
||||
it "doesn't report in macro scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
|
@ -25,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
|
||||
|
|
|
@ -6,41 +6,41 @@ module Ameba::Rule::Lint
|
|||
|
||||
it "passes if percent arrays are written correctly" do
|
||||
s = Source.new %q(
|
||||
%i(one two three)
|
||||
%w(one two three)
|
||||
%i[one two three]
|
||||
%w[one two three]
|
||||
|
||||
%i(1 2 3)
|
||||
%w(1 2 3)
|
||||
%i[1 2 3]
|
||||
%w[1 2 3]
|
||||
|
||||
%i()
|
||||
%w()
|
||||
%i[]
|
||||
%w[]
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
end
|
||||
|
||||
it "fails if string percent array has commas" do
|
||||
s = Source.new %( %w(one, two) )
|
||||
s = Source.new %( %w[one, two] )
|
||||
subject.catch(s).should_not be_valid
|
||||
end
|
||||
|
||||
it "fails if string percent array has quotes" do
|
||||
s = Source.new %( %w("one" "two") )
|
||||
s = Source.new %( %w["one" "two"] )
|
||||
subject.catch(s).should_not be_valid
|
||||
end
|
||||
|
||||
it "fails if symbols percent array has commas" do
|
||||
s = Source.new %( %i(one, two) )
|
||||
s = Source.new %( %i[one, two] )
|
||||
subject.catch(s).should_not be_valid
|
||||
end
|
||||
|
||||
it "fails if symbols percent array has a colon" do
|
||||
s = Source.new %( %i(:one :two) )
|
||||
s = Source.new %( %i[:one :two] )
|
||||
subject.catch(s).should_not be_valid
|
||||
end
|
||||
|
||||
it "reports rule, location and message for %i" do
|
||||
s = Source.new %(
|
||||
%i(:one)
|
||||
%i[:one]
|
||||
), "source.cr"
|
||||
|
||||
subject.catch(s).should_not be_valid
|
||||
|
@ -54,7 +54,7 @@ module Ameba::Rule::Lint
|
|||
|
||||
it "reports rule, location and message for %w" do
|
||||
s = Source.new %(
|
||||
%w("one")
|
||||
%w["one"]
|
||||
), "source.cr"
|
||||
|
||||
subject.catch(s).should_not be_valid
|
||||
|
@ -68,17 +68,17 @@ 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") )
|
||||
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) )
|
||||
s = Source.new %( %i[:one] )
|
||||
rule.catch(s).should be_valid
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,21 +4,21 @@ module Ameba::Rule::Lint
|
|||
describe RedundantStringCoercion do
|
||||
subject = RedundantStringCoercion.new
|
||||
|
||||
it "does not report if there is no redundant string coersion" do
|
||||
it "does not report if there is no redundant string coercion" do
|
||||
s = Source.new %(
|
||||
"Hello, #{name}"
|
||||
)
|
||||
subject.catch(s).should be_valid
|
||||
end
|
||||
|
||||
it "reports if there is a redundant string coersion" do
|
||||
it "reports if there is a redundant string coercion" do
|
||||
s = Source.new %q(
|
||||
"Hello, #{name.to_s}"
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
end
|
||||
|
||||
it "does not report if coersion is used in binary op" do
|
||||
it "does not report if coercion is used in binary op" do
|
||||
s = Source.new %q(
|
||||
"Hello, #{3.to_s + 's'}"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,6 +31,30 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
pending "reports if there is a shadowing in an unpacked variable in a block" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def some_method
|
||||
foo = 1
|
||||
|
||||
[{3}].each do |(foo)|
|
||||
# ^ error: Shadowing outer local variable `foo`
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
pending "reports if there is a shadowing in an unpacked variable in a block (2)" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def some_method
|
||||
foo = 1
|
||||
|
||||
[{[3]}].each do |((foo))|
|
||||
# ^ error: Shadowing outer local variable `foo`
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "does not report outer vars declared below shadowed block" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
methods = klass.methods.select { |m| m.annotation(MyAnn) }
|
||||
|
@ -44,7 +68,7 @@ module Ameba::Rule::Lint
|
|||
foo = 1
|
||||
|
||||
-> (foo : Int32) {}
|
||||
# ^ error: Shadowing outer local variable `foo`
|
||||
# ^^^^^^^^^^^ error: Shadowing outer local variable `foo`
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
@ -69,7 +93,7 @@ module Ameba::Rule::Lint
|
|||
3.times do |foo|
|
||||
# ^ error: Shadowing outer local variable `foo`
|
||||
-> (foo : Int32) { foo + 1 }
|
||||
# ^ error: Shadowing outer local variable `foo`
|
||||
# ^^^^^^^^^^^ error: Shadowing outer local variable `foo`
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
@ -136,6 +160,19 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it shadows type declaration" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class FooBar
|
||||
getter index : String
|
||||
|
||||
def bar
|
||||
3.times do |index|
|
||||
end
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it shadows throwaway arguments" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
data = [{1, "a"}, {2, "b"}, {3, "c"}]
|
||||
|
@ -157,20 +194,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
|
||||
|
|
|
@ -39,7 +39,7 @@ module Ameba::Rule::Lint
|
|||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a shared var in spawn" do
|
||||
it "reports if there is a shared var in spawn (while)" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
i = 0
|
||||
while i < 10
|
||||
|
@ -56,6 +56,24 @@ module Ameba::Rule::Lint
|
|||
expect_no_corrections source
|
||||
end
|
||||
|
||||
it "reports if there is a shared var in spawn (loop)" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
i = 0
|
||||
loop do
|
||||
break if i >= 10
|
||||
spawn do
|
||||
puts(i)
|
||||
# ^ error: Shared variable `i` is used in fiber
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
Fiber.yield
|
||||
CRYSTAL
|
||||
|
||||
expect_no_corrections source
|
||||
end
|
||||
|
||||
it "reports reassigned reference to shared var in spawn" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
channel = Channel(String).new
|
||||
|
@ -194,24 +212,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
|
||||
|
|
46
spec/ameba/rule/lint/spec_filename_spec.cr
Normal file
46
spec/ameba/rule/lint/spec_filename_spec.cr
Normal file
|
@ -0,0 +1,46 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
subject = SpecFilename.new
|
||||
|
||||
describe SpecFilename do
|
||||
it "passes if relative file path does not start with `spec/`" do
|
||||
expect_no_issues subject, code: "", path: "src/spec/foo.cr"
|
||||
expect_no_issues subject, code: "", path: "src/spec/foo/bar.cr"
|
||||
end
|
||||
|
||||
it "passes if file extension is not `.cr`" do
|
||||
expect_no_issues subject, code: "", path: "spec/foo.json"
|
||||
expect_no_issues subject, code: "", path: "spec/foo/bar.json"
|
||||
end
|
||||
|
||||
it "passes if filename is correct" do
|
||||
expect_no_issues subject, code: "", path: "spec/foo_spec.cr"
|
||||
expect_no_issues subject, code: "", path: "spec/foo/bar_spec.cr"
|
||||
end
|
||||
|
||||
it "fails if filename is wrong" do
|
||||
expect_issue subject, <<-CRYSTAL, path: "spec/foo.cr"
|
||||
|
||||
# ^{} error: Spec filename should have `_spec` suffix: foo_spec.cr, not foo.cr
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
context "#ignored_dirs" do
|
||||
it "provide sane defaults" do
|
||||
expect_no_issues subject, code: "", path: "spec/support/foo.cr"
|
||||
expect_no_issues subject, code: "", path: "spec/fixtures/foo.cr"
|
||||
expect_no_issues subject, code: "", path: "spec/data/foo.cr"
|
||||
end
|
||||
end
|
||||
|
||||
context "#ignored_filenames" do
|
||||
it "ignores spec_helper by default" do
|
||||
expect_no_issues subject, code: "", path: "spec/spec_helper.cr"
|
||||
expect_no_issues subject, code: "", path: "spec/foo/spec_helper.cr"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -115,12 +115,12 @@ module Ameba::Rule::Lint
|
|||
|
||||
first.rule.should_not be_nil
|
||||
first.location.to_s.should eq "source_spec.cr:1:11"
|
||||
first.end_location.to_s.should eq ""
|
||||
first.end_location.to_s.should eq "source_spec.cr:1:21"
|
||||
first.message.should eq "Focused spec item detected"
|
||||
|
||||
second.rule.should_not be_nil
|
||||
second.location.to_s.should eq "source_spec.cr:2:13"
|
||||
second.end_location.to_s.should eq ""
|
||||
second.end_location.to_s.should eq "source_spec.cr:2:23"
|
||||
second.message.should eq "Focused spec item detected"
|
||||
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
|
||||
|
|
35
spec/ameba/rule/lint/typos_spec.cr
Normal file
35
spec/ameba/rule/lint/typos_spec.cr
Normal file
|
@ -0,0 +1,35 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
private def check_typos_bin!
|
||||
unless Ameba::Rule::Lint::Typos::BIN_PATH
|
||||
pending! "`typos` executable is not available"
|
||||
end
|
||||
end
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
subject = Typos.new
|
||||
.tap(&.fail_on_error = true)
|
||||
|
||||
describe Typos do
|
||||
it "reports typos" do
|
||||
check_typos_bin!
|
||||
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
# method with no arugments
|
||||
# ^^^^^^^^^ error: Typo found: arugments -> arguments
|
||||
def tpos
|
||||
# ^^^^ error: Typo found: tpos -> typos
|
||||
:otput
|
||||
# ^^^^^ error: Typo found: otput -> output
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
# method with no arguments
|
||||
def typos
|
||||
:output
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
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,146 +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 %(
|
||||
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 "reports if block arg is not used" do
|
||||
s = Source.new %(
|
||||
it "doesn't report if block arg is not used" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&block)
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if unused and there is yield" do
|
||||
s = Source.new %(
|
||||
it "doesn't report if unused and there is yield" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&block)
|
||||
yield 1
|
||||
end
|
||||
)
|
||||
subject.catch(s).should_not be_valid
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it's an anonymous block" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(&)
|
||||
yield 1
|
||||
end
|
||||
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
|
||||
|
@ -163,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
|
||||
), "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"
|
||||
end
|
||||
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 }}
|
||||
|
@ -225,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 %}
|
||||
|
@ -240,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
|
||||
|
|
130
spec/ameba/rule/lint/unused_block_argument_spec.cr
Normal file
130
spec/ameba/rule/lint/unused_block_argument_spec.cr
Normal file
|
@ -0,0 +1,130 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
subject = UnusedBlockArgument.new
|
||||
|
||||
describe UnusedBlockArgument do
|
||||
it "doesn't report if it is an instance var argument" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class A
|
||||
def initialize(&@callback)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if anonymous" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c, &)
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if argument name starts with a `_`" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c, &_block)
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if it is a block and used" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c, &block)
|
||||
block.call
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if block arg is not used" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
def method(a, b, c, &block)
|
||||
# ^^^^^ error: Unused block argument `block`. [...]
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
def method(a, b, c, &_block)
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if unused and there is yield" do
|
||||
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
|
||||
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
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def method(a, b, c, &)
|
||||
yield 1
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if variable is referenced implicitly" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Bar < Foo
|
||||
def method(a, b, c, &block)
|
||||
super
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "doesn't report if used in abstract def" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
abstract def debug(id : String, &on_message: Callback)
|
||||
abstract def info(&on_message: Callback)
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "super" do
|
||||
it "reports if variable is not referenced implicitly by super" do
|
||||
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
|
||||
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
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
macro my_macro(&block)
|
||||
{% block %}
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@ require "../../../spec_helper"
|
|||
|
||||
module Ameba::Rule::Metrics
|
||||
subject = CyclomaticComplexity.new
|
||||
complex_method = <<-CODE
|
||||
complex_method = <<-CRYSTAL
|
||||
def hello(a, b, c)
|
||||
if a && b && c
|
||||
begin
|
||||
|
@ -15,7 +15,7 @@ module Ameba::Rule::Metrics
|
|||
end
|
||||
end
|
||||
end
|
||||
CODE
|
||||
CRYSTAL
|
||||
|
||||
describe CyclomaticComplexity do
|
||||
it "passes for empty methods" do
|
||||
|
|
93
spec/ameba/rule/naming/accessor_method_name_spec.cr
Normal file
93
spec/ameba/rule/naming/accessor_method_name_spec.cr
Normal file
|
@ -0,0 +1,93 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = AccessorMethodName.new
|
||||
|
||||
describe AccessorMethodName do
|
||||
it "passes if accessor method name is correct" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Foo
|
||||
def self.instance
|
||||
end
|
||||
|
||||
def self.instance=(value)
|
||||
end
|
||||
|
||||
def user
|
||||
end
|
||||
|
||||
def user=(user)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "passes if accessor method is defined in top-level scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def get_user
|
||||
end
|
||||
|
||||
def set_user(user)
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "fails if accessor method is defined with receiver in top-level scope" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def Foo.get_user
|
||||
# ^^^^^^^^ error: Favour method name 'user' over 'get_user'
|
||||
end
|
||||
|
||||
def Foo.set_user(user)
|
||||
# ^^^^^^^^ error: Favour method name 'user=' over 'set_user'
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "fails if accessor method name is wrong" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
class Foo
|
||||
def self.get_instance
|
||||
# ^^^^^^^^^^^^ error: Favour method name 'instance' over 'get_instance'
|
||||
end
|
||||
|
||||
def self.set_instance(value)
|
||||
# ^^^^^^^^^^^^ error: Favour method name 'instance=' over 'set_instance'
|
||||
end
|
||||
|
||||
def get_user
|
||||
# ^^^^^^^^ error: Favour method name 'user' over 'get_user'
|
||||
end
|
||||
|
||||
def set_user(user)
|
||||
# ^^^^^^^^ error: Favour method name 'user=' over 'set_user'
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "ignores if alternative name isn't valid syntax" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Foo
|
||||
def get_404
|
||||
end
|
||||
|
||||
def set_404(value)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "ignores if the method has unexpected arity" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Foo
|
||||
def get_user(type)
|
||||
end
|
||||
|
||||
def set_user(user, type)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
151
spec/ameba/rule/naming/ascii_identifiers_spec.cr
Normal file
151
spec/ameba/rule/naming/ascii_identifiers_spec.cr
Normal file
|
@ -0,0 +1,151 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = AsciiIdentifiers.new
|
||||
|
||||
describe AsciiIdentifiers do
|
||||
it "reports classes with names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
class BigAwesome🐺
|
||||
# ^^^^^^^^^^^ error: Identifier contains non-ascii characters
|
||||
@🐺_name : String
|
||||
# ^^^^^^^ error: Identifier contains non-ascii characters
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports modules with names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
module Bąk
|
||||
# ^^^ error: Identifier contains non-ascii characters
|
||||
@@bąk_name : String
|
||||
# ^^^^^^^^^^ error: Identifier contains non-ascii characters
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports enums with names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
enum TypeOf🔥
|
||||
# ^^^^^^^ error: Identifier contains non-ascii characters
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports defs with names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def łódź
|
||||
# ^^^^ error: Identifier contains non-ascii characters
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports defs with parameter names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def forest_adventure(include_🐺 = true, include_🐿 = true)
|
||||
# ^ error: Identifier contains non-ascii characters
|
||||
# ^ error: Identifier contains non-ascii characters
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports defs with parameter default values containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def forest_adventure(animal_type = :🐺)
|
||||
# ^^ error: Identifier contains non-ascii characters
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports argument names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
%w[wensleydale cheddar brie].each { |🧀| nil }
|
||||
# ^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports calls with arguments containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
%i[🐺 🐿].index!(:🐺)
|
||||
# ^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports calls with named arguments containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
%i[🐺 🐿].index!(obj: :🐺)
|
||||
# ^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports aliases with names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
alias JSON🧀 = JSON::Any
|
||||
# ^^^^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports constants with names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
I_LOVE_🍣 = true
|
||||
# ^^^^^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports assignments with variable names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
space_👾 = true
|
||||
# ^^^^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports multiple assignments with variable names containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
foo, space_👾 = true, true
|
||||
# ^^^^^^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports assignments with symbol literals containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
foo = :신장
|
||||
# ^^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports multiple assignments with symbol literals containing non-ascii characters" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
foo, bar = :신장, true
|
||||
# ^^^ error: Identifier contains non-ascii characters
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "passes for strings with non-ascii characters" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
space = "👾"
|
||||
space = :invader # 👾
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
context "#ignore_symbols" do
|
||||
it "returns `false` by default" do
|
||||
rule = AsciiIdentifiers.new
|
||||
rule.ignore_symbols?.should be_false
|
||||
end
|
||||
|
||||
it "stops reporting symbol literals if set to `true`" do
|
||||
rule = AsciiIdentifiers.new
|
||||
rule.ignore_symbols = true
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
def forest_adventure(animal_type = :🐺); end
|
||||
%i[🐺 🐿].index!(:🐺)
|
||||
foo, bar = :신장, true
|
||||
foo = :신장
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = BinaryOperatorParameterName.new
|
||||
|
||||
describe BinaryOperatorParameterName do
|
||||
it "ignores `other` parameter name in binary method definitions" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def +(other); end
|
||||
def -(other); end
|
||||
def *(other); end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "ignores binary method definitions with arity other than 1" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def +; end
|
||||
def +(foo, bar); end
|
||||
def -; end
|
||||
def -(foo, bar); end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "ignores non-binary method definitions" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def foo(bar); end
|
||||
def bąk(genus); end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports binary methods definitions with incorrectly named parameter" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def +(foo); end
|
||||
# ^ error: When defining the `+` operator, name its argument `other`
|
||||
def -(foo); end
|
||||
# ^ error: When defining the `-` operator, name its argument `other`
|
||||
def *(foo); end
|
||||
# ^ error: When defining the `*` operator, name its argument `other`
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "ignores methods from #excluded_operators" do
|
||||
subject.excluded_operators.each do |op|
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def #{op}(foo); end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
100
spec/ameba/rule/naming/block_parameter_name_spec.cr
Normal file
100
spec/ameba/rule/naming/block_parameter_name_spec.cr
Normal file
|
@ -0,0 +1,100 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = BlockParameterName.new
|
||||
.tap(&.min_name_length = 3)
|
||||
.tap(&.allowed_names = %w[_ e i j k v])
|
||||
|
||||
describe BlockParameterName do
|
||||
it "passes if block parameter name matches #allowed_names" do
|
||||
subject.allowed_names.each do |name|
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
%w[].each { |#{name}| }
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails if block parameter name doesn't match #allowed_names" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
# ^ error: Disallowed block parameter name found
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
context "#min_name_length" do
|
||||
it "allows setting custom values" do
|
||||
rule = BlockParameterName.new
|
||||
rule.allowed_names = %w[a b c]
|
||||
|
||||
rule.min_name_length = 3
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
# ^ error: Disallowed block parameter name found
|
||||
CRYSTAL
|
||||
|
||||
rule.min_name_length = 1
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "#allow_names_ending_in_numbers" do
|
||||
it "allows setting custom values" do
|
||||
rule = BlockParameterName.new
|
||||
rule.min_name_length = 1
|
||||
rule.allowed_names = %w[]
|
||||
|
||||
rule.allow_names_ending_in_numbers = false
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
%w[].each { |x1| }
|
||||
# ^ error: Disallowed block parameter name found
|
||||
CRYSTAL
|
||||
|
||||
rule.allow_names_ending_in_numbers = true
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
%w[].each { |x1| }
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "#allowed_names" do
|
||||
it "allows setting custom names" do
|
||||
rule = BlockParameterName.new
|
||||
rule.min_name_length = 3
|
||||
|
||||
rule.allowed_names = %w[a b c]
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
# ^ error: Disallowed block parameter name found
|
||||
CRYSTAL
|
||||
|
||||
rule.allowed_names = %w[x y z]
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "#forbidden_names" do
|
||||
it "allows setting custom names" do
|
||||
rule = BlockParameterName.new
|
||||
rule.min_name_length = 1
|
||||
rule.allowed_names = %w[]
|
||||
|
||||
rule.forbidden_names = %w[x y z]
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
# ^ error: Disallowed block parameter name found
|
||||
CRYSTAL
|
||||
|
||||
rule.forbidden_names = %w[a b c]
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
%w[].each { |x| }
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
41
spec/ameba/rule/naming/constant_names_spec.cr
Normal file
41
spec/ameba/rule/naming/constant_names_spec.cr
Normal file
|
@ -0,0 +1,41 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Naming::ConstantNames.new
|
||||
|
||||
private def it_reports_constant(name, value, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports constant name #{expected}", file, line do
|
||||
rule = Rule::Naming::ConstantNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
|
||||
%{name} = #{value}
|
||||
# ^{name} error: Constant name should be screaming-cased: #{expected}, not #{name}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Naming::ConstantNames do
|
||||
it "passes if type names are screaming-cased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
LUCKY_NUMBERS = [3, 7, 11]
|
||||
DOCUMENTATION_URL = "https://crystal-lang.org/docs"
|
||||
|
||||
Int32
|
||||
|
||||
s : String = "str"
|
||||
|
||||
def works(n : Int32)
|
||||
end
|
||||
|
||||
Log = ::Log.for("db")
|
||||
|
||||
a = 1
|
||||
myVar = 2
|
||||
m_var = 3
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
# it_reports_constant "MyBadConstant", "1", "MYBADCONSTANT"
|
||||
it_reports_constant "Wrong_NAME", "2", "WRONG_NAME"
|
||||
it_reports_constant "Wrong_Name", "3", "WRONG_NAME"
|
||||
end
|
||||
end
|
19
spec/ameba/rule/naming/filename_spec.cr
Normal file
19
spec/ameba/rule/naming/filename_spec.cr
Normal file
|
@ -0,0 +1,19 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = Filename.new
|
||||
|
||||
describe Filename do
|
||||
it "passes if filename is correct" do
|
||||
expect_no_issues subject, code: "", path: "src/foo.cr"
|
||||
expect_no_issues subject, code: "", path: "src/foo_bar.cr"
|
||||
end
|
||||
|
||||
it "fails if filename is wrong" do
|
||||
expect_issue subject, <<-CRYSTAL, path: "src/fooBar.cr"
|
||||
|
||||
# ^{} error: Filename should be underscore-cased: foo_bar.cr, not fooBar.cr
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
42
spec/ameba/rule/naming/method_names_spec.cr
Normal file
42
spec/ameba/rule/naming/method_names_spec.cr
Normal file
|
@ -0,0 +1,42 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Naming::MethodNames.new
|
||||
|
||||
private def it_reports_method_name(name, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports method name #{expected}", file, line do
|
||||
rule = Rule::Naming::MethodNames.new
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Naming::MethodNames do
|
||||
it "passes if method names are underscore-cased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Person
|
||||
def first_name
|
||||
end
|
||||
|
||||
def date_of_birth
|
||||
end
|
||||
|
||||
def homepage_url
|
||||
end
|
||||
|
||||
def valid?
|
||||
end
|
||||
|
||||
def name
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it_reports_method_name "firstName", "first_name"
|
||||
it_reports_method_name "date_of_Birth", "date_of_birth"
|
||||
it_reports_method_name "homepageURL", "homepage_url"
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Style
|
||||
module Ameba::Rule::Naming
|
||||
subject = PredicateName.new
|
||||
|
||||
describe PredicateName do
|
||||
|
@ -21,30 +21,22 @@ module Ameba::Rule::Style
|
|||
|
||||
it "fails if predicate name is wrong" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
class Image
|
||||
def self.is_valid?(x)
|
||||
# ^^^^^^^^^ error: Favour method name 'valid?' over 'is_valid?'
|
||||
end
|
||||
end
|
||||
|
||||
def is_valid?(x)
|
||||
# ^^^^^^^^^^^^^^ error: Favour method name 'valid?' over 'is_valid?'
|
||||
# ^^^^^^^^^ error: Favour method name 'valid?' over 'is_valid?'
|
||||
end
|
||||
|
||||
def is_valid(x)
|
||||
# ^^^^^^^^ error: Favour method name 'valid?' over 'is_valid'
|
||||
end
|
||||
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
|
72
spec/ameba/rule/naming/query_bool_methods_spec.cr
Normal file
72
spec/ameba/rule/naming/query_bool_methods_spec.cr
Normal file
|
@ -0,0 +1,72 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = QueryBoolMethods.new
|
||||
|
||||
describe QueryBoolMethods do
|
||||
it "passes for valid cases" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Foo
|
||||
class_property? foo = true
|
||||
property? foo = true
|
||||
property foo2 : Bool? = true
|
||||
setter panda = true
|
||||
end
|
||||
|
||||
module Bar
|
||||
class_getter? bar : Bool = true
|
||||
getter? bar : Bool
|
||||
getter bar2 : Bool? = true
|
||||
setter panda : Bool = true
|
||||
|
||||
def initialize(@bar = true)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports only valid properties" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
class Foo
|
||||
class_property? foo = true
|
||||
class_property bar = true
|
||||
# ^^^ error: Consider using 'class_property?' for 'bar'
|
||||
class_property baz = true
|
||||
# ^^^ error: Consider using 'class_property?' for 'baz'
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
{% for call in %w[getter class_getter property class_property] %}
|
||||
it "reports `{{ call.id }}` assign with Bool" do
|
||||
expect_issue subject, <<-CRYSTAL, call: {{ call }}
|
||||
class Foo
|
||||
%{call} foo = true
|
||||
_{call} # ^^^ error: Consider using '%{call}?' for 'foo'
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports `{{ call.id }}` type declaration assign with Bool" do
|
||||
expect_issue subject, <<-CRYSTAL, call: {{ call }}
|
||||
class Foo
|
||||
%{call} foo : Bool = true
|
||||
_{call} # ^^^ error: Consider using '%{call}?' for 'foo'
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports `{{ call.id }}` type declaration with Bool" do
|
||||
expect_issue subject, <<-CRYSTAL, call: {{ call }}
|
||||
class Foo
|
||||
%{call} foo : Bool
|
||||
_{call} # ^^^ error: Consider using '%{call}?' for 'foo'
|
||||
|
||||
def initialize(@foo = true)
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Naming
|
||||
subject = RescuedExceptionsVariableName.new
|
||||
|
||||
describe RescuedExceptionsVariableName do
|
||||
it "passes if exception handler variable name matches #allowed_names" do
|
||||
subject.allowed_names.each do |name|
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
def foo
|
||||
raise "foo"
|
||||
rescue #{name}
|
||||
nil
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails if exception handler variable name doesn't match #allowed_names" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
def foo
|
||||
raise "foo"
|
||||
rescue wtf
|
||||
# ^^^^^^^^ error: Disallowed variable name, use one of these instead: 'e', 'ex', 'exception', 'error'
|
||||
nil
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
context "#allowed_names" do
|
||||
it "returns sensible defaults" do
|
||||
rule = RescuedExceptionsVariableName.new
|
||||
rule.allowed_names.should eq %w[e ex exception error]
|
||||
end
|
||||
|
||||
it "allows setting custom names" do
|
||||
rule = RescuedExceptionsVariableName.new
|
||||
rule.allowed_names = %w[foo]
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
def foo
|
||||
raise "foo"
|
||||
rescue e
|
||||
# ^^^^^^ error: Disallowed variable name, use 'foo' instead
|
||||
nil
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
53
spec/ameba/rule/naming/type_names_spec.cr
Normal file
53
spec/ameba/rule/naming/type_names_spec.cr
Normal file
|
@ -0,0 +1,53 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Naming::TypeNames.new
|
||||
|
||||
private def it_reports_name(type, name, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports type name #{expected}", file, line do
|
||||
rule = Rule::Naming::TypeNames.new
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Naming::TypeNames do
|
||||
it "passes if type names are camelcased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class ParseError < Exception
|
||||
end
|
||||
|
||||
module HTTP
|
||||
class RequestHandler
|
||||
end
|
||||
end
|
||||
|
||||
alias NumericValue = Float32 | Float64 | Int32 | Int64
|
||||
|
||||
lib LibYAML
|
||||
end
|
||||
|
||||
struct TagDirective
|
||||
end
|
||||
|
||||
enum Time::DayOfWeek
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it_reports_name "class", "My_class", "MyClass"
|
||||
it_reports_name "module", "HTT_p", "HTTP"
|
||||
it_reports_name "lib", "Lib_YAML", "LibYAML"
|
||||
it_reports_name "struct", "Tag_directive", "TagDirective"
|
||||
it_reports_name "enum", "Time_enum::Day_of_week", "TimeEnum::DayOfWeek"
|
||||
|
||||
it "reports alias name" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
alias Numeric_value = Int32
|
||||
# ^^^^^^^^^^^^^ error: Type name should be camelcased: NumericValue, but it was Numeric_value
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +1,19 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Style::VariableNames.new
|
||||
subject = Rule::Naming::VariableNames.new
|
||||
|
||||
private def it_reports_var_name(name, value, expected)
|
||||
it "reports variable name #{expected}" do
|
||||
rule = Rule::Style::VariableNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name
|
||||
private def it_reports_var_name(name, value, expected, *, file = __FILE__, line = __LINE__)
|
||||
it "reports variable name #{expected}", file, line do
|
||||
rule = Rule::Naming::VariableNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line
|
||||
%{name} = #{value}
|
||||
# ^{name} error: Var name should be underscore-cased: #{expected}, not %{name}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Style::VariableNames do
|
||||
describe Rule::Naming::VariableNames do
|
||||
it "passes if var names are underscore-cased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Greeting
|
||||
|
@ -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
|
|
@ -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,9 +46,9 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure object_call_names" do
|
||||
rule = Rule::Performance::AnyAfterFilter.new
|
||||
rule.filter_names = %w(select)
|
||||
it "#filter_names" do
|
||||
rule = AnyAfterFilter.new
|
||||
rule.filter_names = %w[select]
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
[1, 2, 3].reject { |e| e > 2 }.any?
|
||||
|
@ -60,16 +60,6 @@ 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?`
|
||||
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
|
||||
|
||||
|
@ -77,3 +67,4 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
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
|
||||
end
|
||||
|
|
|
@ -44,9 +44,9 @@ 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)
|
||||
rule.call_names = %w[uniq]
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
[1, 2, 3].select { |e| e > 2 }.reverse
|
||||
|
@ -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
|
||||
|
|
57
spec/ameba/rule/performance/excessive_allocations_spec.cr
Normal file
57
spec/ameba/rule/performance/excessive_allocations_spec.cr
Normal file
|
@ -0,0 +1,57 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Performance
|
||||
subject = ExcessiveAllocations.new
|
||||
|
||||
describe ExcessiveAllocations do
|
||||
it "passes if there is no potential performance improvements" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
"Alice".chars.each(arg) { |c| puts c }
|
||||
"Alice".chars(arg).each { |c| puts c }
|
||||
"Alice\nBob".lines.each(arg) { |l| puts l }
|
||||
"Alice\nBob".lines(arg).each { |l| puts l }
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a collection method followed by each" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
"Alice".chars.each { |c| puts c }
|
||||
# ^^^^^^^^^^ error: Use `each_char {...}` instead of `chars.each {...}` to avoid excessive allocation
|
||||
"Alice\nBob".lines.each { |l| puts l }
|
||||
# ^^^^^^^^^^ error: Use `each_line {...}` instead of `lines.each {...}` to avoid excessive allocation
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
"Alice".each_char { |c| puts c }
|
||||
"Alice\nBob".each_line { |l| puts l }
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "does not report if source is a spec" do
|
||||
expect_no_issues subject, <<-CRYSTAL, "source_spec.cr"
|
||||
"Alice".chars.each { |c| puts c }
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
it "#call_names" do
|
||||
rule = ExcessiveAllocations.new
|
||||
rule.call_names = {
|
||||
"children" => "each_child",
|
||||
}
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
"Alice".chars.each { |c| puts c }
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "macro" do
|
||||
it "doesn't report in macro scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
{{ "Alice".chars.each { |c| puts c } }}
|
||||
CRYSTAL
|
||||
end
|
||||
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,9 +62,9 @@ module Ameba::Rule::Performance
|
|||
end
|
||||
|
||||
context "properties" do
|
||||
it "allows to configure object_call_names" do
|
||||
rule = Rule::Performance::FirstLastAfterFilter.new
|
||||
rule.filter_names = %w(reject)
|
||||
it "#filter_names" do
|
||||
rule = FirstLastAfterFilter.new
|
||||
rule.filter_names = %w[reject]
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
[1, 2, 3].select { |e| e > 2 }.first
|
||||
|
@ -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
|
||||
|
|
45
spec/ameba/rule/performance/minmax_after_map_spec.cr
Normal file
45
spec/ameba/rule/performance/minmax_after_map_spec.cr
Normal file
|
@ -0,0 +1,45 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Performance
|
||||
subject = MinMaxAfterMap.new
|
||||
|
||||
describe MinMaxAfterMap do
|
||||
it "passes if there are no potential performance improvements" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
%w[Alice Bob].map { |name| name.size }.min(2)
|
||||
%w[Alice Bob].map { |name| name.size }.max(2)
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a `min/max/minmax` call followed by `map`" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
%w[Alice Bob].map { |name| name.size }.min
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `min_of {...}` instead of `map {...}.min`.
|
||||
%w[Alice Bob].map(&.size).max.zero?
|
||||
# ^^^^^^^^^^^^^^^ error: Use `max_of {...}` instead of `map {...}.max`.
|
||||
%w[Alice Bob].map(&.size).minmax?
|
||||
# ^^^^^^^^^^^^^^^^^^^ error: Use `minmax_of? {...}` instead of `map {...}.minmax?`.
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
%w[Alice Bob].min_of { |name| name.size }
|
||||
%w[Alice Bob].max_of(&.size).zero?
|
||||
%w[Alice Bob].minmax_of?(&.size)
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "does not report if source is a spec" do
|
||||
expect_no_issues subject, path: "source_spec.cr", code: <<-CRYSTAL
|
||||
%w[Alice Bob].map(&.size).min
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
context "macro" do
|
||||
it "doesn't report in macro scope" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
{{ %w[Alice Bob].map(&.size).min }}
|
||||
CRYSTAL
|
||||
end
|
||||
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,21 +32,21 @@ 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
|
||||
rule.filter_names = %w(select)
|
||||
it "#filter_names" do
|
||||
rule = SizeAfterFilter.new
|
||||
rule.filter_names = %w[select]
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
[1, 2, 3].reject(&.empty?).size
|
||||
|
@ -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
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Style::ConstantNames.new
|
||||
|
||||
private def it_reports_constant(name, value, expected)
|
||||
it "reports constant name #{expected}" do
|
||||
rule = Rule::Style::ConstantNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name
|
||||
%{name} = #{value}
|
||||
# ^{name} error: Constant name should be screaming-cased: #{expected}, not #{name}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Style::ConstantNames do
|
||||
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"
|
||||
|
||||
Int32
|
||||
|
||||
s : String = "str"
|
||||
|
||||
def works(n : Int32)
|
||||
end
|
||||
|
||||
Log = ::Log.for("db")
|
||||
|
||||
a = 1
|
||||
myVar = 2
|
||||
m_var = 3
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
|
|
@ -14,31 +14,25 @@ module Ameba::Rule::Style
|
|||
end
|
||||
|
||||
it "reports if there is a call to is_a?(Nil) without receiver" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
a = is_a?(Nil)
|
||||
# ^^^ error: Use `nil?` instead of `is_a?(Nil)`
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
a = self.nil?
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "reports if there is a call to is_a?(Nil) with receiver" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
a.is_a?(Nil)
|
||||
# ^^^ error: Use `nil?` instead of `is_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
|
||||
expect_correction source, <<-CRYSTAL
|
||||
a.nil?
|
||||
CRYSTAL
|
||||
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,23 +119,11 @@ 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)
|
||||
expect_no_issues rule, "1200000"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Style::MethodNames.new
|
||||
|
||||
private def it_reports_method_name(name, expected)
|
||||
it "reports method name #{expected}" do
|
||||
rule = Rule::Style::MethodNames.new
|
||||
expect_issue rule, <<-CRYSTAL, name: name
|
||||
def %{name}; end
|
||||
# ^{name} error: Method name should be underscore-cased: #{expected}, not %{name}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Style::MethodNames do
|
||||
it "passes if method names are underscore-cased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class Person
|
||||
def first_name
|
||||
end
|
||||
|
||||
def date_of_birth
|
||||
end
|
||||
|
||||
def homepage_url
|
||||
end
|
||||
|
||||
def valid?
|
||||
end
|
||||
|
||||
def name
|
||||
end
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
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
|
||||
|
|
120
spec/ameba/rule/style/parentheses_around_condition_spec.cr
Normal file
120
spec/ameba/rule/style/parentheses_around_condition_spec.cr
Normal file
|
@ -0,0 +1,120 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Style
|
||||
subject = ParenthesesAroundCondition.new
|
||||
|
||||
describe ParenthesesAroundCondition do
|
||||
{% for keyword in %w[if unless while until] %}
|
||||
context "{{ keyword.id }}" do
|
||||
it "reports if redundant parentheses are found" do
|
||||
source = expect_issue subject, <<-CRYSTAL, keyword: {{ keyword }}
|
||||
%{keyword} (foo > 10)
|
||||
_{keyword} # ^^^^^^^^^^ error: Redundant parentheses
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
{{ keyword.id }} foo > 10
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
|
||||
context "case" do
|
||||
it "reports if redundant parentheses are found" do
|
||||
source = expect_issue subject, <<-CRYSTAL
|
||||
case (foo = @foo)
|
||||
# ^^^^^^^^^^^^ error: Redundant parentheses
|
||||
when String then "string"
|
||||
when Symbol then "symbol"
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_correction source, <<-CRYSTAL
|
||||
case foo = @foo
|
||||
when String then "string"
|
||||
when Symbol then "symbol"
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
context "#exclude_ternary" do
|
||||
it "skips ternary control expressions by default" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
(foo > bar) ? true : false
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "allows to configure assignments" do
|
||||
rule = ParenthesesAroundCondition.new
|
||||
rule.exclude_ternary = false
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
(foo.empty?) ? true : false
|
||||
# ^^^^^^^^^^ error: Redundant parentheses
|
||||
CRYSTAL
|
||||
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
(foo && bar) ? true : false
|
||||
(foo || bar) ? true : false
|
||||
(foo = @foo) ? true : false
|
||||
foo == 42 ? true : false
|
||||
(foo = 42) ? true : false
|
||||
(foo > 42) ? true : false
|
||||
(foo >= 42) ? true : false
|
||||
(3 >= foo >= 42) ? true : false
|
||||
(3.in? 0..42) ? true : false
|
||||
(yield 42) ? true : false
|
||||
(foo rescue 42) ? true : false
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "#allow_safe_assignment" do
|
||||
it "reports assignments by default" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
if (foo = @foo)
|
||||
# ^^^^^^^^^^^^ error: Redundant parentheses
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
if !(foo = @foo)
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
if foo = @foo
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it "allows to configure assignments" do
|
||||
rule = ParenthesesAroundCondition.new
|
||||
rule.allow_safe_assignment = true
|
||||
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
if foo = @foo
|
||||
# ^^^^^^^^^^ error: Missing parentheses
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
if (foo = @foo)
|
||||
foo
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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
|
||||
|
|
|
@ -146,7 +146,7 @@ module Ameba::Rule::Style
|
|||
end
|
||||
end
|
||||
|
||||
context "expception handler" do
|
||||
context "exception handler" do
|
||||
it "doesn't report if there is no redundant next in exception handler" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
block do |v|
|
||||
|
@ -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
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba
|
||||
subject = Rule::Style::TypeNames.new
|
||||
|
||||
private def it_reports_name(type, name, expected)
|
||||
it "reports type name #{expected}" do
|
||||
rule = Rule::Style::TypeNames.new
|
||||
expect_issue rule, <<-CRYSTAL, type: type, name: name
|
||||
%{type} %{name}; end
|
||||
# ^{type}^{name}^^^^ error: Type name should be camelcased: #{expected}, but it was %{name}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
describe Rule::Style::TypeNames do
|
||||
it "passes if type names are camelcased" do
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
class ParseError < Exception
|
||||
end
|
||||
|
||||
module HTTP
|
||||
class RequestHandler
|
||||
end
|
||||
end
|
||||
|
||||
alias NumericValue = Float32 | Float64 | Int32 | Int64
|
||||
|
||||
lib LibYAML
|
||||
end
|
||||
|
||||
struct TagDirective
|
||||
end
|
||||
|
||||
enum Time::DayOfWeek
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
it_reports_name "class", "My_class", "MyClass"
|
||||
it_reports_name "module", "HTT_p", "HTTP"
|
||||
it_reports_name "lib", "Lib_YAML", "LibYAML"
|
||||
it_reports_name "struct", "Tag_directive", "TagDirective"
|
||||
it_reports_name "enum", "Time_enum::Day_of_week", "TimeEnum::DayOfWeek"
|
||||
|
||||
it "reports alias name" do
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
alias Numeric_value = Int32
|
||||
# ^{} 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
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue