Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e6d67c169 | ||
|
|
614ae049e6 | ||
|
|
b1492cb0cf | ||
|
|
237a71311c | ||
|
|
e40b280fd8 | ||
|
|
308255bda8 | ||
|
|
1e81a5b9a5 | ||
|
|
0627d7b0d4 | ||
|
|
99ec7522d3 | ||
|
|
d30c5c0185 | ||
|
|
3fc334ebc3 | ||
|
|
df8955c0dd | ||
|
|
6b87a54825 | ||
|
|
72ec902554 | ||
|
|
2c9fff0f93 | ||
|
|
c42f7af02b | ||
|
|
9c77960a26 | ||
|
|
a20f413841 | ||
|
|
ffbae20014 | ||
|
|
9c81578ab5 | ||
|
|
a9f7b8e18c | ||
|
|
61c3c34f44 | ||
|
|
d8faf5bb7b | ||
|
|
9d03032cfd | ||
|
|
3c4c68b3d5 | ||
|
|
bed0a68176 | ||
|
|
60642c340d | ||
|
|
35e4263934 | ||
|
|
43327512da | ||
|
|
32692f14ff | ||
|
|
f80bd3c478 | ||
|
|
23934b48ba | ||
|
|
c370a8a724 | ||
|
|
8c2237f80d | ||
|
|
a5e9c39023 | ||
|
|
6d918f5fb4 | ||
|
|
4347468d8a | ||
|
|
f044def5a1 | ||
|
|
05c9ccad12 | ||
|
|
7533e56c1b | ||
|
|
3f083795b6 | ||
|
|
1bbc959d1a | ||
|
|
1603f21b0a | ||
|
|
02c3957667 | ||
|
|
eeab9f5aa1 | ||
|
|
3f3e987a23 | ||
|
|
53c8b27ee2 | ||
|
|
3550dd10af | ||
|
|
7e44b88122 |
@@ -0,0 +1,87 @@
|
||||
name: builds.hex.pm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
ELIXIR_OPTS: "--warnings-as-errors"
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
concurrency: builds_txt
|
||||
|
||||
jobs:
|
||||
release_pre_built:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- otp: 23
|
||||
otp_version: '23.3'
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- uses: ./.github/workflows/release_pre_built
|
||||
with:
|
||||
otp_version: ${{ matrix.otp_version }}
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
- name: Utils.sh
|
||||
run: |
|
||||
cat << 'EOF' > utils.sh
|
||||
function purge_key() {
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Fastly-Key: ${FASTLY_KEY}" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Length: 0" \
|
||||
"https://api.fastly.com/service/$1/purge/$2"
|
||||
}
|
||||
function purge() {
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
}
|
||||
EOF
|
||||
chmod +x utils.sh
|
||||
- name: Upload Docs to S3
|
||||
if: ${{ matrix.build_docs }}
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
run: |
|
||||
source utils.sh
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
for f in doc/*; do
|
||||
if [ -d "$f" ]; then
|
||||
app=`echo $f | sed s/"doc\/"//`
|
||||
tarball="${app}-${version}.tar.gz"
|
||||
surrogate_key="docs/${app}-${version}"
|
||||
tar -czf "${tarball}" -C "doc/${app}" .
|
||||
aws s3 cp "${tarball}" "s3://${{ env.AWS_S3_BUCKET }}/docs/${tarball}" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
purge "${surrogate_key}"
|
||||
fi
|
||||
done
|
||||
@@ -13,8 +13,9 @@ jobs:
|
||||
name: Linux, ${{ matrix.otp_release }}, Ubuntu 16.04
|
||||
continue-on-error: ${{ matrix.development }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
otp_release: ['OTP-23.0', 'OTP-22.3', 'OTP-22.0', 'OTP-21.3.8', 'OTP-21.0']
|
||||
otp_release: ['OTP-24.0', 'OTP-23.0', 'OTP-22.3', 'OTP-22.0', 'OTP-21.3.8', 'OTP-21.0']
|
||||
development: [false]
|
||||
include:
|
||||
- otp_release: master
|
||||
@@ -23,17 +24,17 @@ jobs:
|
||||
development: true
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Install Erlang/OTP
|
||||
run: |
|
||||
cd $RUNNER_TEMP
|
||||
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-14.04/${{ matrix.otp_release }}.tar.gz
|
||||
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-16.04/${{ matrix.otp_release }}.tar.gz
|
||||
mkdir -p otp
|
||||
tar zxf otp.tar.gz -C otp --strip-components=1
|
||||
otp/Install -minimal $(pwd)/otp
|
||||
echo "::add-path::$(pwd)/otp/bin"
|
||||
echo "$(pwd)/otp/bin" >> $GITHUB_PATH
|
||||
- name: Compile Elixir
|
||||
run: |
|
||||
rm -rf .git
|
||||
@@ -61,7 +62,7 @@ jobs:
|
||||
steps:
|
||||
- name: Configure Git
|
||||
run: git config --global core.autocrlf input
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Cache Erlang/OTP package
|
||||
@@ -88,7 +89,7 @@ jobs:
|
||||
name: Check POSIX-compliant
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Install Shellcheck
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: "Release pre built"
|
||||
description: "Builds elixir release, ExDoc and generates docs"
|
||||
inputs:
|
||||
otp:
|
||||
description: "The major OTP version"
|
||||
otp_version:
|
||||
description: "The exact OTP version (major.minor[.patch])"
|
||||
build_docs:
|
||||
description: "If docs have to be built or not"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ inputs.otp_version }}
|
||||
version-type: strict
|
||||
- name: Build Elixir Release
|
||||
shell: bash
|
||||
run: |
|
||||
make Precompiled.zip
|
||||
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
- name: Get latest stable ExDoc version
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
EX_DOC_LATEST_STABLE_VERSION=$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
|
||||
echo "EX_DOC_LATEST_STABLE_VERSION=${EX_DOC_LATEST_STABLE_VERSION}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
if: ${{ inputs.build_docs }}
|
||||
with:
|
||||
repository: elixir-lang/ex_doc
|
||||
ref: v${{ env.EX_DOC_LATEST_STABLE_VERSION }}
|
||||
path: ex_doc
|
||||
- name: Build ex_doc
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
mv ex_doc ../ex_doc
|
||||
cd ../ex_doc
|
||||
../elixir/bin/mix do local.rebar --force, local.hex --force, deps.get, compile
|
||||
cd ../elixir
|
||||
- name: Build Docs
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
make Docs.zip
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
+64
-2
@@ -14,7 +14,7 @@ Elixir v1.11 builds on top of the recently added compilation tracers to track ap
|
||||
|
||||
These two conditions may seem contradictory. After all, if a module is available, it must have come from a dependency. This is not true in two scenarios:
|
||||
|
||||
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not explicitly listed as a dependency
|
||||
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not listed as a dependency
|
||||
|
||||
* In an umbrella project, because all child applications are compiled within the same VM, you may have a module from a sibling project available, even if you don't depend on said sibling
|
||||
|
||||
@@ -36,7 +36,7 @@ on :ssl. To fix this, you must do one of:
|
||||
to your "def project" in mix.exs
|
||||
```
|
||||
|
||||
This comes with extra benefits in umbrella projects, as it requires child applications to explicitly list their dependencies, completely rejecting cyclic dependencies between siblings.
|
||||
This comes with extra benefits in umbrella projects, as it requires applications to depend on the siblings they depend on, which will fail if there are any cyclic dependencies.
|
||||
|
||||
## Compiler checks: data constructors
|
||||
|
||||
@@ -243,6 +243,68 @@ The Calendar module ships with a new `Calendar.strftime/3` function, which provi
|
||||
|
||||
Mix also includes two new tasks: `mix app.config`, for application runtime configuration, and `mix test.coverage`, which generates aggregated coverage reports for umbrella projects and for test suites partitioned across processes.
|
||||
|
||||
## v1.11.4 (2021-03-16)
|
||||
|
||||
This release introduces fixes for better support of Erlang/OTP 24+.
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Update formatting when printing warnings and errors from Erlang/OTP 24+
|
||||
* [Kernel] Support float-16 on bitstrings
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix local.rebar] This task will now install rebar3 version 3.14.4, compiled with Erlang/OTP 21
|
||||
|
||||
## v1.11.3 (2021-01-04)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Macro] Add `Macro.unique_var/2` and `Macro.generate_unique_arguments/2`
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Do not raise when checking for operator ambiguity when `:static_atoms_encoder` is set in `Code.string_to_quoted`
|
||||
* [Kernel] Emit undefined function warnings from `with`
|
||||
* [Kernel] Do not fail type checking when literals are used in guards
|
||||
* [Module] Do not warn for attributes used in `@after_compile`
|
||||
* [Record] Make sure nested record names do not clobber each other
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Do not crash if there are macros and module attributes on the left side of `++`
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Do not use Unicode chars if ANSI is disabled
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps.compile] Fix compatibility with rebar v3.14
|
||||
* [mix release] Do not use private `ram_file:compress/1`
|
||||
* [mix xref] Do not crash when retrieving calls for modules in memory
|
||||
|
||||
## v1.11.2 (2020-11-03)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Do not crash when getting docs for missing `erts` appdir
|
||||
* [Kernel] Raise meaningful error if `:erlang.is_record` is used in guards
|
||||
* [Kernel] Prune tracers when fetching `__ENV__` inside functions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix] Fix regression where aliases could not call themselves recursively
|
||||
* [mix compile] Do not discard tracers that are set programatically
|
||||
|
||||
## v1.11.1 (2020-10-16)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
@@ -178,40 +178,48 @@ clean_residual_files:
|
||||
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
|
||||
|
||||
DOCS_FORMAT = html
|
||||
COMPILE_DOCS = bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
|
||||
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
|
||||
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
|
||||
|
||||
docs_elixir: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (elixir)"
|
||||
$(Q) rm -rf doc/elixir
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel,--config "lib/elixir/docs.exs")
|
||||
$(call DOCS_COMPILE,Elixir,elixir,Kernel,--config "lib/elixir/scripts/elixir_docs.exs")
|
||||
$(call DOCS_CONFIG,elixir)
|
||||
|
||||
docs_eex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (eex)"
|
||||
$(Q) rm -rf doc/eex
|
||||
$(call COMPILE_DOCS,EEx,eex,EEx)
|
||||
$(call DOCS_COMPILE,EEx,eex,EEx,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,eex)
|
||||
|
||||
docs_mix: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (mix)"
|
||||
$(Q) rm -rf doc/mix
|
||||
$(call COMPILE_DOCS,Mix,mix,Mix)
|
||||
$(call DOCS_COMPILE,Mix,mix,Mix,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,mix)
|
||||
|
||||
docs_iex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (iex)"
|
||||
$(Q) rm -rf doc/iex
|
||||
$(call COMPILE_DOCS,IEx,iex,IEx)
|
||||
$(call DOCS_COMPILE,IEx,iex,IEx,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,iex)
|
||||
|
||||
docs_ex_unit: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (ex_unit)"
|
||||
$(Q) rm -rf doc/ex_unit
|
||||
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit)
|
||||
$(call DOCS_COMPILE,ExUnit,ex_unit,ExUnit,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,ex_unit)
|
||||
|
||||
docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (logger)"
|
||||
$(Q) rm -rf doc/logger
|
||||
$(call COMPILE_DOCS,Logger,logger,Logger)
|
||||
$(call DOCS_COMPILE,Logger,logger,Logger,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -220,23 +228,23 @@ docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
#==> Zip tasks
|
||||
|
||||
Docs.zip: docs
|
||||
rm -f Docs-v$(VERSION).zip
|
||||
zip -9 -r Docs-v$(VERSION).zip CHANGELOG.md doc NOTICE LICENSE README.md
|
||||
@ echo "Docs file created $(CURDIR)/Docs-v$(VERSION).zip"
|
||||
rm -f Docs.zip
|
||||
zip -9 -r Docs.zip CHANGELOG.md doc NOTICE LICENSE README.md
|
||||
@ echo "Docs file created $(CURDIR)/Docs.zip"
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -f Precompiled-v$(VERSION).zip
|
||||
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
|
||||
rm -f Precompiled.zip
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
|
||||
|
||||
zips: Precompiled.zip Docs.zip
|
||||
@ echo ""
|
||||
@ echo "### Checksums"
|
||||
@ echo ""
|
||||
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
|
||||
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
|
||||
@ shasum -a 1 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
|
||||
@ shasum -a 512 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
|
||||
@ shasum -a 1 < Precompiled.zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
|
||||
@ shasum -a 512 < Precompiled.zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
|
||||
@ shasum -a 1 < Docs.zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
|
||||
@ shasum -a 512 < Docs.zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
|
||||
@ echo ""
|
||||
|
||||
#==> Test tasks
|
||||
|
||||
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
|
||||
# Generates the buffers by handling each expression from the tokenizer.
|
||||
# It returns Macro.t/0 or it raises.
|
||||
|
||||
defp generate_buffer([{:text, chars} | rest], buffer, scope, state) do
|
||||
defp generate_buffer([{:text, _line, _column, chars} | rest], buffer, scope, state) do
|
||||
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
@@ -179,7 +179,7 @@ defmodule EEx.Compiler do
|
||||
# Look middle expressions that immediately follow a start_expr
|
||||
|
||||
defp look_ahead_middle(
|
||||
[{:text, text}, {:middle_expr, line, _column, _, chars} | rest] = tokens,
|
||||
[{:text, _, _, text}, {:middle_expr, line, _, _, chars} | rest] = tokens,
|
||||
start,
|
||||
contents
|
||||
) do
|
||||
|
||||
@@ -6,7 +6,7 @@ defmodule EEx.Tokenizer do
|
||||
@type column :: non_neg_integer
|
||||
@type marker :: '=' | '/' | '|' | ''
|
||||
@type token ::
|
||||
{:text, content}
|
||||
{:text, line, column, content}
|
||||
| {:expr | :start_expr | :middle_expr | :end_expr, line, column, marker, content}
|
||||
| {:eof, line, column}
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
It returns {:ok, list} with the following tokens:
|
||||
|
||||
* `{:text, content}`
|
||||
* `{:text, line, column, content}`
|
||||
* `{:expr, line, column, marker, content}`
|
||||
* `{:start_expr, line, column, marker, content}`
|
||||
* `{:middle_expr, line, column, marker, content}`
|
||||
@@ -36,8 +36,11 @@ defmodule EEx.Tokenizer do
|
||||
def tokenize(list, line, column, opts)
|
||||
when is_list(list) and is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
|
||||
column = opts.indentation + column
|
||||
{list, line, column} = (opts.trim && trim_init(list, line, column)) || {list, line, column}
|
||||
tokenize(list, line, column, opts, [], [])
|
||||
|
||||
{list, line, column} =
|
||||
(opts.trim && trim_init(list, line, column, opts)) || {list, line, column}
|
||||
|
||||
tokenize(list, line, column, opts, [{line, column}], [])
|
||||
end
|
||||
|
||||
defp tokenize('<%%' ++ t, line, column, opts, buffer, acc) do
|
||||
@@ -53,7 +56,8 @@ defmodule EEx.Tokenizer do
|
||||
{rest, new_line, new_column, buffer} =
|
||||
trim_if_needed(rest, new_line, new_column, opts, buffer)
|
||||
|
||||
tokenize(rest, new_line, new_column, opts, buffer, acc)
|
||||
acc = tokenize_text(buffer, acc)
|
||||
tokenize(rest, new_line, new_column, opts, [{new_line, new_column}], acc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -76,7 +80,7 @@ defmodule EEx.Tokenizer do
|
||||
|
||||
acc = tokenize_text(buffer, acc)
|
||||
final = {key, line, column, marker, expr}
|
||||
tokenize(rest, new_line, new_column, opts, [], [final | acc])
|
||||
tokenize(rest, new_line, new_column, opts, [{new_line, new_column}], [final | acc])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -164,49 +168,67 @@ defmodule EEx.Tokenizer do
|
||||
# Tokenize the buffered text by appending
|
||||
# it to the given accumulator.
|
||||
|
||||
defp tokenize_text([], acc) do
|
||||
defp tokenize_text([{_line, _column}], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp tokenize_text(buffer, acc) do
|
||||
[{:text, Enum.reverse(buffer)} | acc]
|
||||
[{line, column} | buffer] = Enum.reverse(buffer)
|
||||
[{:text, line, column, buffer} | acc]
|
||||
end
|
||||
|
||||
defp trim_if_needed(rest, line, column, opts, buffer) do
|
||||
if opts.trim do
|
||||
buffer = trim_left(buffer, 0)
|
||||
{rest, line, column} = trim_right(rest, line, column, 0)
|
||||
{rest, line, column} = trim_right(rest, line, column, 0, opts)
|
||||
{rest, line, column, buffer}
|
||||
else
|
||||
{rest, line, column, buffer}
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_init([h | t], line, column) when h in @spaces, do: trim_init(t, line, column + 1)
|
||||
defp trim_init([?\r, ?\n | t], line, _column), do: trim_init(t, line + 1, 1)
|
||||
defp trim_init([?\n | t], line, _column), do: trim_init(t, line + 1, 1)
|
||||
defp trim_init([?<, ?% | _] = rest, line, column), do: {rest, line, column}
|
||||
defp trim_init(_, _, _), do: false
|
||||
defp trim_init([h | t], line, column, opts) when h in @spaces,
|
||||
do: trim_init(t, line, column + 1, opts)
|
||||
|
||||
defp trim_init([?\r, ?\n | t], line, _column, opts),
|
||||
do: trim_init(t, line + 1, opts.indentation + 1, opts)
|
||||
|
||||
defp trim_init([?\n | t], line, _column, opts),
|
||||
do: trim_init(t, line + 1, opts.indentation + 1, opts)
|
||||
|
||||
defp trim_init([?<, ?% | _] = rest, line, column, _opts),
|
||||
do: {rest, line, column}
|
||||
|
||||
defp trim_init(_, _, _, _), do: false
|
||||
|
||||
defp trim_left(buffer, count) do
|
||||
case trim_whitespace(buffer) do
|
||||
[?\n, ?\r | rest] -> trim_left(rest, count + 1)
|
||||
[?\n | rest] -> trim_left(rest, count + 1)
|
||||
case trim_whitespace(buffer, 0) do
|
||||
{[?\n, ?\r | rest], _} -> trim_left(rest, count + 1)
|
||||
{[?\n | rest], _} -> trim_left(rest, count + 1)
|
||||
_ when count > 0 -> [?\n | buffer]
|
||||
_ -> buffer
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_right(rest, line, column, count) do
|
||||
case trim_whitespace(rest) do
|
||||
[?\r, ?\n | rest] -> trim_right(rest, line + 1, 1, count + 1)
|
||||
[?\n | rest] -> trim_right(rest, line + 1, 1, count + 1)
|
||||
[] -> {[], line, column + length(rest)}
|
||||
_ when count > 0 -> {[?\n | rest], line - 1, column}
|
||||
_ -> {rest, line, column}
|
||||
defp trim_right(rest, line, column, last_column, opts) do
|
||||
case trim_whitespace(rest, column) do
|
||||
{[?\r, ?\n | rest], column} ->
|
||||
trim_right(rest, line + 1, opts.indentation + 1, column + 1, opts)
|
||||
|
||||
{[?\n | rest], column} ->
|
||||
trim_right(rest, line + 1, opts.indentation + 1, column, opts)
|
||||
|
||||
{[], column} ->
|
||||
{[], line, column}
|
||||
|
||||
_ when last_column > 0 ->
|
||||
{[?\n | rest], line - 1, last_column}
|
||||
|
||||
_ ->
|
||||
{rest, line, column}
|
||||
end
|
||||
end
|
||||
|
||||
defp trim_whitespace([h | t]) when h in @spaces, do: trim_whitespace(t)
|
||||
defp trim_whitespace(list), do: list
|
||||
defp trim_whitespace([h | t], column) when h in @spaces, do: trim_whitespace(t, column + 1)
|
||||
defp trim_whitespace(list, column), do: {list, column}
|
||||
end
|
||||
|
||||
@@ -7,36 +7,36 @@ defmodule EEx.TokenizerTest do
|
||||
@opts %{indentation: 0, trim: false}
|
||||
|
||||
test "simple chars lists" do
|
||||
assert T.tokenize('foo', 1, 1, @opts) == {:ok, [{:text, 'foo'}, {:eof, 1, 4}]}
|
||||
assert T.tokenize('foo', 1, 1, @opts) == {:ok, [{:text, 1, 1, 'foo'}, {:eof, 1, 4}]}
|
||||
end
|
||||
|
||||
test "simple strings" do
|
||||
assert T.tokenize("foo", 1, 1, @opts) == {:ok, [{:text, 'foo'}, {:eof, 1, 4}]}
|
||||
assert T.tokenize("foo", 1, 1, @opts) == {:ok, [{:text, 1, 1, 'foo'}, {:eof, 1, 4}]}
|
||||
end
|
||||
|
||||
test "strings with embedded code" do
|
||||
assert T.tokenize('foo <% bar %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '', ' bar '}, {:eof, 1, 14}]}
|
||||
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '', ' bar '}, {:eof, 1, 14}]}
|
||||
end
|
||||
|
||||
test "strings with embedded equals code" do
|
||||
assert T.tokenize('foo <%= bar %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '=', ' bar '}, {:eof, 1, 15}]}
|
||||
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '=', ' bar '}, {:eof, 1, 15}]}
|
||||
end
|
||||
|
||||
test "strings with embedded slash code" do
|
||||
assert T.tokenize('foo <%/ bar %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '/', ' bar '}, {:eof, 1, 15}]}
|
||||
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '/', ' bar '}, {:eof, 1, 15}]}
|
||||
end
|
||||
|
||||
test "strings with embedded pipe code" do
|
||||
assert T.tokenize('foo <%| bar %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '|', ' bar '}, {:eof, 1, 15}]}
|
||||
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '|', ' bar '}, {:eof, 1, 15}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line" do
|
||||
assert T.tokenize('foo\n<%= bar %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo\n'}, {:expr, 2, 1, '=', ' bar '}, {:eof, 2, 11}]}
|
||||
{:ok, [{:text, 1, 1, 'foo\n'}, {:expr, 2, 1, '=', ' bar '}, {:eof, 2, 11}]}
|
||||
end
|
||||
|
||||
test "strings with more than one line and expression with more than one line" do
|
||||
@@ -48,11 +48,11 @@ defmodule EEx.TokenizerTest do
|
||||
'''
|
||||
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:expr, 1, 5, '=', ' bar\n\nbaz '},
|
||||
{:text, '\n'},
|
||||
{:text, 3, 7, '\n'},
|
||||
{:expr, 4, 1, '', ' foo '},
|
||||
{:text, '\n'},
|
||||
{:text, 4, 10, '\n'},
|
||||
{:eof, 5, 1}
|
||||
]
|
||||
|
||||
@@ -61,21 +61,21 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "quotation" do
|
||||
assert T.tokenize('foo <%% true %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo <% true %>'}, {:eof, 1, 16}]}
|
||||
{:ok, [{:text, 1, 1, 'foo <% true %>'}, {:eof, 1, 16}]}
|
||||
end
|
||||
|
||||
test "quotation with do/end" do
|
||||
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1, 1, @opts) ==
|
||||
{:ok, [{:text, 'foo <% true do %>bar<% end %>'}, {:eof, 1, 32}]}
|
||||
{:ok, [{:text, 1, 1, 'foo <% true do %>bar<% end %>'}, {:eof, 1, 32}]}
|
||||
end
|
||||
|
||||
test "quotation with interpolation" do
|
||||
exprs = [
|
||||
{:text, 'a <% b '},
|
||||
{:text, 1, 1, 'a <% b '},
|
||||
{:expr, 1, 9, '=', ' c '},
|
||||
{:text, ' '},
|
||||
{:text, 1, 17, ' '},
|
||||
{:expr, 1, 18, '=', ' d '},
|
||||
{:text, ' e %> f'},
|
||||
{:text, 1, 26, ' e %> f'},
|
||||
{:eof, 1, 33}
|
||||
]
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "improperly formatted quotation with interpolation" do
|
||||
exprs = [
|
||||
{:text, '<%% a <%= b %> c %>'},
|
||||
{:text, 1, 1, '<%% a <%= b %> c %>'},
|
||||
{:eof, 1, 22}
|
||||
]
|
||||
|
||||
@@ -93,7 +93,7 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "eex comments" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:eof, 1, 16}
|
||||
]
|
||||
|
||||
@@ -102,7 +102,8 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "eex comments with do/end" do
|
||||
exprs = [
|
||||
{:text, 'foo bar'},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:text, 1, 19, 'bar'},
|
||||
{:eof, 1, 32}
|
||||
]
|
||||
|
||||
@@ -111,7 +112,7 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "elixir comments" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:expr, 1, 5, [], ' true # this is a boolean '},
|
||||
{:eof, 1, 35}
|
||||
]
|
||||
@@ -122,7 +123,7 @@ defmodule EEx.TokenizerTest do
|
||||
test "elixir comments with do/end" do
|
||||
exprs = [
|
||||
{:start_expr, 1, 1, [], ' if true do # startif '},
|
||||
{:text, 'text'},
|
||||
{:text, 1, 27, 'text'},
|
||||
{:end_expr, 1, 31, [], ' end # closeif '},
|
||||
{:eof, 1, 50}
|
||||
]
|
||||
@@ -133,9 +134,9 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "strings with embedded do end" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:start_expr, 1, 5, '', ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:text, 1, 21, 'bar'},
|
||||
{:end_expr, 1, 24, '', ' end '},
|
||||
{:eof, 1, 33}
|
||||
]
|
||||
@@ -145,12 +146,12 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "strings with embedded -> end" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:start_expr, 1, 5, '', ' cond do '},
|
||||
{:middle_expr, 1, 18, '', ' false -> '},
|
||||
{:text, 'bar'},
|
||||
{:text, 1, 32, 'bar'},
|
||||
{:middle_expr, 1, 35, '', ' true -> '},
|
||||
{:text, 'baz'},
|
||||
{:text, 1, 48, 'baz'},
|
||||
{:end_expr, 1, 51, '', ' end '},
|
||||
{:eof, 1, 60}
|
||||
]
|
||||
@@ -162,9 +163,9 @@ defmodule EEx.TokenizerTest do
|
||||
test "strings with multiple callbacks" do
|
||||
exprs = [
|
||||
{:start_expr, 1, 1, '=', ' a fn -> '},
|
||||
{:text, 'foo'},
|
||||
{:text, 1, 15, 'foo'},
|
||||
{:middle_expr, 1, 18, '', ' end, fn -> '},
|
||||
{:text, 'bar'},
|
||||
{:text, 1, 34, 'bar'},
|
||||
{:end_expr, 1, 37, '', ' end '},
|
||||
{:eof, 1, 46}
|
||||
]
|
||||
@@ -176,9 +177,9 @@ defmodule EEx.TokenizerTest do
|
||||
test "strings with callback followed by do block" do
|
||||
exprs = [
|
||||
{:start_expr, 1, 1, '=', ' a fn -> '},
|
||||
{:text, 'foo'},
|
||||
{:text, 1, 15, 'foo'},
|
||||
{:middle_expr, 1, 18, '', ' end do '},
|
||||
{:text, 'bar'},
|
||||
{:text, 1, 30, 'bar'},
|
||||
{:end_expr, 1, 33, '', ' end '},
|
||||
{:eof, 1, 42}
|
||||
]
|
||||
@@ -188,11 +189,11 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "strings with embedded keywords blocks" do
|
||||
exprs = [
|
||||
{:text, 'foo '},
|
||||
{:text, 1, 1, 'foo '},
|
||||
{:start_expr, 1, 5, '', ' if true do '},
|
||||
{:text, 'bar'},
|
||||
{:text, 1, 21, 'bar'},
|
||||
{:middle_expr, 1, 24, '', ' else '},
|
||||
{:text, 'baz'},
|
||||
{:text, 1, 34, 'baz'},
|
||||
{:end_expr, 1, 37, '', ' end '},
|
||||
{:eof, 1, 46}
|
||||
]
|
||||
@@ -206,9 +207,9 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
exprs = [
|
||||
{:start_expr, 1, 2, '=', ' if true do '},
|
||||
{:text, '\n TRUE \n'},
|
||||
{:text, 1, 20, '\n TRUE \n'},
|
||||
{:middle_expr, 3, 3, '', ' else '},
|
||||
{:text, '\n FALSE \n'},
|
||||
{:text, 3, 13, '\n FALSE \n'},
|
||||
{:end_expr, 5, 3, '', ' end '},
|
||||
{:eof, 7, 3}
|
||||
]
|
||||
@@ -218,7 +219,7 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "trim mode with comment" do
|
||||
exprs = [
|
||||
{:text, '\n123'},
|
||||
{:text, 1, 19, '\n123'},
|
||||
{:eof, 2, 4}
|
||||
]
|
||||
|
||||
@@ -227,9 +228,9 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "trim mode with CRLF" do
|
||||
exprs = [
|
||||
{:text, '0\n'},
|
||||
{:text, 1, 1, '0\n'},
|
||||
{:expr, 2, 3, '=', ' 12 '},
|
||||
{:text, '\n34'},
|
||||
{:text, 2, 15, '\n34'},
|
||||
{:eof, 3, 3}
|
||||
]
|
||||
|
||||
@@ -238,9 +239,9 @@ defmodule EEx.TokenizerTest do
|
||||
|
||||
test "trim mode set to false" do
|
||||
exprs = [
|
||||
{:text, ' '},
|
||||
{:text, 1, 1, ' '},
|
||||
{:expr, 1, 2, '=', ' 12 '},
|
||||
{:text, ' \n'},
|
||||
{:text, 1, 11, ' \n'},
|
||||
{:eof, 2, 1}
|
||||
]
|
||||
|
||||
|
||||
+10
-2
@@ -1373,8 +1373,16 @@ defmodule Code do
|
||||
:error ->
|
||||
case :code.which(module) do
|
||||
:preloaded ->
|
||||
path = Path.join([:code.lib_dir(:erts), "doc", "chunks", "#{module}.chunk"])
|
||||
fetch_docs_from_chunk(path)
|
||||
# The erts directory is not necessarily included in releases
|
||||
# unless it is listed as an extra application.
|
||||
case :code.lib_dir(:erts) do
|
||||
path when is_list(path) ->
|
||||
path = Path.join([path, "doc", "chunks", "#{module}.chunk"])
|
||||
fetch_docs_from_chunk(path)
|
||||
|
||||
{:error, _} ->
|
||||
{:error, :chunk_not_found}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :module_not_found}
|
||||
|
||||
+21
-9
@@ -473,15 +473,25 @@ defmodule Enum do
|
||||
@doc """
|
||||
Chunks the `enumerable` with fine grained control when every chunk is emitted.
|
||||
|
||||
`chunk_fun` receives the current element and the accumulator and
|
||||
must return `{:cont, chunk, acc}` to emit the given chunk and
|
||||
continue with accumulator or `{:cont, acc}` to not emit any chunk
|
||||
and continue with the return accumulator.
|
||||
`chunk_fun` receives the current element and the accumulator and must return:
|
||||
|
||||
`after_fun` is invoked when iteration is done and must also return
|
||||
`{:cont, chunk, acc}` or `{:cont, acc}`.
|
||||
* `{:cont, chunk, acc}` to emit a chunk and continue with the accumulator
|
||||
* `{:cont, acc}` to not emit any chunk and continue with the accumulator
|
||||
* `{:halt, acc}` to halt chunking over the `enumerable`.
|
||||
|
||||
Returns a list of lists.
|
||||
`after_fun` is invoked with the final accumulator when iteration is
|
||||
finished (or `halt`ed) to handle any trailing elements that were returned
|
||||
as part of an accumulator, but were not emited as a chunk by `chunk_fun`.
|
||||
It must return:
|
||||
|
||||
* `{:cont, chunk, acc}` to emit a chunk. The chunk will be appended to the
|
||||
list of already emitted chunks.
|
||||
* `{:cont, acc}` to not emit a chunk
|
||||
|
||||
The `acc` in `after_fun` is required in order to mirror the tuple format
|
||||
from `chunk_fun` but it will be discarded since the traversal is complete.
|
||||
|
||||
Returns a list of emitted chunks.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -498,6 +508,8 @@ defmodule Enum do
|
||||
...> end
|
||||
iex> Enum.chunk_while(1..10, [], chunk_fun, after_fun)
|
||||
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
|
||||
iex> Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun)
|
||||
[[1, 2], [3, 5, 7]]
|
||||
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@@ -512,7 +524,7 @@ defmodule Enum do
|
||||
{_, {res, acc}} =
|
||||
Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {buffer, acc} ->
|
||||
case chunk_fun.(entry, acc) do
|
||||
{:cont, emit, acc} -> {:cont, {[emit | buffer], acc}}
|
||||
{:cont, chunk, acc} -> {:cont, {[chunk | buffer], acc}}
|
||||
{:cont, acc} -> {:cont, {buffer, acc}}
|
||||
{:halt, acc} -> {:halt, {buffer, acc}}
|
||||
end
|
||||
@@ -520,7 +532,7 @@ defmodule Enum do
|
||||
|
||||
case after_fun.(acc) do
|
||||
{:cont, _acc} -> :lists.reverse(res)
|
||||
{:cont, elem, _acc} -> :lists.reverse([elem | res])
|
||||
{:cont, chunk, _acc} -> :lists.reverse([chunk | res])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ defmodule File do
|
||||
@type stream_mode ::
|
||||
encoding_mode()
|
||||
| :append
|
||||
| :compressed
|
||||
| :trim_bom
|
||||
| {:read_ahead, pos_integer | false}
|
||||
| {:delayed_write, non_neg_integer, non_neg_integer}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
defmodule IO.ANSI.Docs do
|
||||
@moduledoc false
|
||||
|
||||
@bullet_text "• "
|
||||
@bullet_text_unicode "• "
|
||||
@bullet_text_ascii "* "
|
||||
@bullets [?*, ?-, ?+]
|
||||
@spaces [" ", "\n", "\t"]
|
||||
|
||||
@@ -219,7 +220,10 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
|
||||
defp traverse_erlang_html({:dt, _, entries}, indent, options) do
|
||||
["#{indent} ", @bullet_text | handle_erlang_html_text(entries, indent <> " ", options)]
|
||||
[
|
||||
"#{indent} ",
|
||||
bullet_text(options) | handle_erlang_html_text(entries, indent <> " ", options)
|
||||
]
|
||||
end
|
||||
|
||||
defp traverse_erlang_html({:dd, _, entries}, indent, options) do
|
||||
@@ -240,7 +244,10 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
else
|
||||
for {:li, _, lines} <- entries do
|
||||
["#{indent} ", @bullet_text | handle_erlang_html_text(lines, indent <> " ", options)]
|
||||
[
|
||||
"#{indent} ",
|
||||
bullet_text(options) | handle_erlang_html_text(lines, indent <> " ", options)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -424,7 +431,7 @@ defmodule IO.ANSI.Docs do
|
||||
case stripped do
|
||||
<<bullet, ?\s, item::binary>> when bullet in @bullets ->
|
||||
write_text(text, indent, options)
|
||||
process_list(@bullet_text, item, rest, count, indent, options)
|
||||
process_list(bullet_text(options), item, rest, count, indent, options)
|
||||
|
||||
<<d1, ?., ?\s, item::binary>> when d1 in ?0..?9 ->
|
||||
write_text(text, indent, options)
|
||||
@@ -930,6 +937,10 @@ defmodule IO.ANSI.Docs do
|
||||
end
|
||||
end
|
||||
|
||||
defp bullet_text(options) do
|
||||
if options[:enabled], do: @bullet_text_unicode, else: @bullet_text_ascii
|
||||
end
|
||||
|
||||
defp color(style, colors) do
|
||||
IO.ANSI.format_fragment(colors[style], colors[:enabled])
|
||||
end
|
||||
|
||||
@@ -3123,7 +3123,15 @@ defmodule Kernel do
|
||||
|
||||
not function? and __CALLER__.context == :match ->
|
||||
raise ArgumentError,
|
||||
"invalid write attribute syntax, you probably meant to use: @#{name} expression"
|
||||
"""
|
||||
invalid write attribute syntax. If you want to define an attribute, don't do this:
|
||||
|
||||
@foo = :value
|
||||
|
||||
Instead, do this:
|
||||
|
||||
@foo :value
|
||||
"""
|
||||
|
||||
# Typespecs attributes are currently special cased by the compiler
|
||||
is_list(args) and typespec?(name) ->
|
||||
@@ -3219,7 +3227,26 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
# All other cases
|
||||
# Error cases
|
||||
defp do_at([{call, meta, ctx_or_args}, [{:do, _} | _] = kw], _meta, name, _function?, _env) do
|
||||
args =
|
||||
cond do
|
||||
is_atom(ctx_or_args) -> []
|
||||
true -> ctx_or_args
|
||||
end
|
||||
|
||||
code = "\n@#{name} (#{Macro.to_string({call, meta, args ++ [kw]})})"
|
||||
|
||||
raise ArgumentError, """
|
||||
expected 0 or 1 argument for @#{name}, got 2.
|
||||
|
||||
It seems you are trying to use the do-syntax with @module attributes \
|
||||
but the do-block is binding to the attribute name. You probably want \
|
||||
to wrap the argument value in parentheses, like this:
|
||||
#{String.replace(code, "\n", "\n ")}
|
||||
"""
|
||||
end
|
||||
|
||||
defp do_at(args, _meta, name, _function?, _env) do
|
||||
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
|
||||
end
|
||||
|
||||
@@ -233,9 +233,9 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
Sizes for types are a bit more nuanced. The default size for integers is 8.
|
||||
|
||||
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
|
||||
For floats, it is 64. For floats, `size * unit` must result in 16, 32, or 64,
|
||||
corresponding to [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)
|
||||
binary32 and binary64, respectively.
|
||||
binary16, binary32, and binary64, respectively.
|
||||
|
||||
For binaries, the default is the size of the binary. Only the last binary in a
|
||||
match can use the default size. All others must have their size specified
|
||||
|
||||
+58
-7
@@ -337,8 +337,13 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates AST nodes for a given number of required argument variables using
|
||||
`Macro.var/2`.
|
||||
Generates AST nodes for a given number of required argument
|
||||
variables using `Macro.var/2`.
|
||||
|
||||
Note the arguments are not unique. If you later on want
|
||||
to access this same varibles, you can invoke this function
|
||||
with the same inputs. Use `generate_unique_arguments/2` to
|
||||
generate a unique arguments that can't be overridden.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -349,19 +354,47 @@ defmodule Macro do
|
||||
@doc since: "1.5.0"
|
||||
@spec generate_arguments(0, context :: atom) :: []
|
||||
@spec generate_arguments(pos_integer, context) :: [{atom, [], context}, ...] when context: atom
|
||||
def generate_arguments(amount, context)
|
||||
def generate_arguments(amount, context), do: generate_arguments(amount, context, &var/2)
|
||||
|
||||
def generate_arguments(0, context) when is_atom(context), do: []
|
||||
@doc """
|
||||
Generates AST nodes for a given number of required argument
|
||||
variables using `Macro.unique_var/2`.
|
||||
|
||||
def generate_arguments(amount, context)
|
||||
when is_integer(amount) and amount > 0 and is_atom(context) do
|
||||
for id <- 1..amount, do: var(String.to_atom("arg" <> Integer.to_string(id)), context)
|
||||
## Examples
|
||||
|
||||
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
|
||||
iex> {:arg1, [counter: c1], __MODULE__} = var1
|
||||
iex> {:arg2, [counter: c2], __MODULE__} = var2
|
||||
iex> is_integer(c1) and is_integer(c2)
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.11.3"
|
||||
@spec generate_unique_arguments(0, context :: atom) :: []
|
||||
@spec generate_unique_arguments(pos_integer, context) :: [
|
||||
{atom, [counter: integer], context},
|
||||
...
|
||||
]
|
||||
when context: atom
|
||||
def generate_unique_arguments(amount, context),
|
||||
do: generate_arguments(amount, context, &unique_var/2)
|
||||
|
||||
defp generate_arguments(0, context, _fun) when is_atom(context), do: []
|
||||
|
||||
defp generate_arguments(amount, context, fun)
|
||||
when is_integer(amount) and amount > 0 and is_atom(context) do
|
||||
for id <- 1..amount, do: fun.(String.to_atom("arg" <> Integer.to_string(id)), context)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates an AST node representing the variable given
|
||||
by the atoms `var` and `context`.
|
||||
|
||||
Note this variable is not unique. If you later on want
|
||||
to access this same varible, you can invoke `var/2`
|
||||
again with the same argument. Use `unique_var/2` to
|
||||
generate a unique variable that can't be overridden.
|
||||
|
||||
## Examples
|
||||
|
||||
In order to build a variable, a context is expected.
|
||||
@@ -383,6 +416,24 @@ defmodule Macro do
|
||||
{var, [], context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates an AST node representing a unique variable
|
||||
given by the atoms `var` and `context`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
|
||||
iex> is_integer(c)
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.11.3"
|
||||
@spec unique_var(var, context) :: {var, [counter: integer], context}
|
||||
when var: atom, context: atom
|
||||
def unique_var(var, context) when is_atom(var) and is_atom(context) do
|
||||
{var, [counter: :elixir_module.next_counter(context)], context}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a depth-first traversal of quoted expressions
|
||||
using an accumulator.
|
||||
|
||||
@@ -306,7 +306,7 @@ defmodule Module.Types.Expr do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case reduce_ok(clauses, context, &with_clause(&1, stack, &2)) do
|
||||
{:ok, _context} -> {:ok, :dynamic, context}
|
||||
{:ok, context} -> {:ok, :dynamic, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,105 +24,6 @@ defmodule Module.Types.Pattern do
|
||||
of_pattern(pattern, %{stack | context: :pattern}, context)
|
||||
end
|
||||
|
||||
# :atom
|
||||
def of_pattern(atom, _stack, context) when is_atom(atom) do
|
||||
{:ok, {:atom, atom}, context}
|
||||
end
|
||||
|
||||
# 12
|
||||
def of_pattern(literal, _stack, context) when is_integer(literal) do
|
||||
{:ok, :integer, context}
|
||||
end
|
||||
|
||||
# 1.2
|
||||
def of_pattern(literal, _stack, context) when is_float(literal) do
|
||||
{:ok, :float, context}
|
||||
end
|
||||
|
||||
# "..."
|
||||
def of_pattern(literal, _stack, context) when is_binary(literal) do
|
||||
{:ok, :binary, context}
|
||||
end
|
||||
|
||||
# <<...>>>
|
||||
def of_pattern({:<<>>, _meta, args}, stack, context) do
|
||||
result = Of.binary(args, stack, context, &of_pattern/3)
|
||||
|
||||
case result do
|
||||
{:ok, context} -> {:ok, :binary, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# left | []
|
||||
def of_pattern({:|, _meta, [left_expr, []]} = expr, stack, context) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
of_pattern(left_expr, stack, context)
|
||||
end
|
||||
|
||||
# left | right
|
||||
def of_pattern({:|, _meta, [left_expr, right_expr]} = expr, stack, context) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case of_pattern(left_expr, stack, context) do
|
||||
{:ok, left, context} ->
|
||||
case of_pattern(right_expr, stack, context) do
|
||||
{:ok, {:list, right}, context} ->
|
||||
{:ok, to_union([left, right], context), context}
|
||||
|
||||
{:ok, right, context} ->
|
||||
{:ok, to_union([left, right], context), context}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# []
|
||||
def of_pattern([], _stack, context) do
|
||||
{:ok, {:list, :dynamic}, context}
|
||||
end
|
||||
|
||||
# [expr, ...]
|
||||
def of_pattern(exprs, stack, context) when is_list(exprs) do
|
||||
stack = push_expr_stack(exprs, stack)
|
||||
|
||||
case map_reduce_ok(exprs, context, &of_pattern(&1, stack, &2)) do
|
||||
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# left ++ right
|
||||
def of_pattern(
|
||||
{{:., _meta1, [:erlang, :++]}, _meta2, [left_expr, right_expr]} = expr,
|
||||
stack,
|
||||
context
|
||||
) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case of_pattern(left_expr, stack, context) do
|
||||
{:ok, {:list, left}, context} ->
|
||||
case of_pattern(right_expr, stack, context) do
|
||||
{:ok, {:list, right}, context} ->
|
||||
{:ok, {:list, to_union([left, right], context)}, context}
|
||||
|
||||
{:ok, right, context} ->
|
||||
{:ok, {:list, to_union([left, right], context)}, context}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# _
|
||||
def of_pattern({:_, _meta, atom}, _stack, context) when is_atom(atom) do
|
||||
{:ok, :dynamic, context}
|
||||
@@ -139,21 +40,6 @@ defmodule Module.Types.Pattern do
|
||||
{:ok, type, context}
|
||||
end
|
||||
|
||||
# {left, right}
|
||||
def of_pattern({left, right}, stack, context) do
|
||||
of_pattern({:{}, [], [left, right]}, stack, context)
|
||||
end
|
||||
|
||||
# {...}
|
||||
def of_pattern({:{}, _meta, exprs} = expr, stack, context) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case map_reduce_ok(exprs, context, &of_pattern(&1, stack, &2)) do
|
||||
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# left = right
|
||||
def of_pattern({:=, _meta, [left_expr, right_expr]} = expr, stack, context) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
@@ -163,23 +49,6 @@ defmodule Module.Types.Pattern do
|
||||
do: unify(left_type, right_type, stack, context)
|
||||
end
|
||||
|
||||
# %{...}
|
||||
def of_pattern({:%{}, _meta, args} = expr, stack, context) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
Of.open_map(args, stack, context, &of_pattern/3)
|
||||
end
|
||||
|
||||
# %Struct{...}
|
||||
def of_pattern({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context)
|
||||
when is_atom(module) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
with {:ok, struct, context} <- Of.struct(module, meta1, context),
|
||||
{:ok, map, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
|
||||
unify(map, struct, stack, context)
|
||||
end
|
||||
end
|
||||
|
||||
# %_{...}
|
||||
def of_pattern(
|
||||
{:%, _meta1, [{:_, _meta2, var_context}, {:%{}, _meta3, args}]} = expr,
|
||||
@@ -195,7 +64,8 @@ defmodule Module.Types.Pattern do
|
||||
end
|
||||
|
||||
# %var{...} and %^var{...}
|
||||
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context) do
|
||||
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context)
|
||||
when not is_atom(var) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
with {:ok, var_type, context} = of_pattern(var, stack, context),
|
||||
@@ -205,6 +75,10 @@ defmodule Module.Types.Pattern do
|
||||
end
|
||||
end
|
||||
|
||||
def of_pattern(expr, stack, context) do
|
||||
of_shared(expr, stack, context, &of_pattern/3)
|
||||
end
|
||||
|
||||
def unify_kinds(:required, _), do: :required
|
||||
def unify_kinds(_, :required), do: :required
|
||||
def unify_kinds(:optional, :optional), do: :optional
|
||||
@@ -401,10 +275,8 @@ defmodule Module.Types.Pattern do
|
||||
{:ok, type, context}
|
||||
end
|
||||
|
||||
# other literals
|
||||
def of_guard(expr, stack, context) do
|
||||
# Fall back to of_pattern/3 for literals
|
||||
of_pattern(expr, stack, context)
|
||||
of_shared(expr, stack, context, &of_guard/3)
|
||||
end
|
||||
|
||||
defp fresh_context(context) do
|
||||
@@ -550,4 +422,136 @@ defmodule Module.Types.Pattern do
|
||||
defp type_guard?(name) do
|
||||
name in @type_guards
|
||||
end
|
||||
|
||||
## Shared
|
||||
|
||||
# :atom
|
||||
defp of_shared(atom, _stack, context, _fun) when is_atom(atom) do
|
||||
{:ok, {:atom, atom}, context}
|
||||
end
|
||||
|
||||
# 12
|
||||
defp of_shared(literal, _stack, context, _fun) when is_integer(literal) do
|
||||
{:ok, :integer, context}
|
||||
end
|
||||
|
||||
# 1.2
|
||||
defp of_shared(literal, _stack, context, _fun) when is_float(literal) do
|
||||
{:ok, :float, context}
|
||||
end
|
||||
|
||||
# "..."
|
||||
defp of_shared(literal, _stack, context, _fun) when is_binary(literal) do
|
||||
{:ok, :binary, context}
|
||||
end
|
||||
|
||||
# <<...>>>
|
||||
defp of_shared({:<<>>, _meta, args}, stack, context, fun) do
|
||||
case Of.binary(args, stack, context, fun) do
|
||||
{:ok, context} -> {:ok, :binary, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# left | []
|
||||
defp of_shared({:|, _meta, [left_expr, []]} = expr, stack, context, fun) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
fun.(left_expr, stack, context)
|
||||
end
|
||||
|
||||
# left | right
|
||||
defp of_shared({:|, _meta, [left_expr, right_expr]} = expr, stack, context, fun) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case fun.(left_expr, stack, context) do
|
||||
{:ok, left, context} ->
|
||||
case fun.(right_expr, stack, context) do
|
||||
{:ok, {:list, right}, context} ->
|
||||
{:ok, to_union([left, right], context), context}
|
||||
|
||||
{:ok, right, context} ->
|
||||
{:ok, to_union([left, right], context), context}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# []
|
||||
defp of_shared([], _stack, context, _fun) do
|
||||
{:ok, {:list, :dynamic}, context}
|
||||
end
|
||||
|
||||
# [expr, ...]
|
||||
defp of_shared(exprs, stack, context, fun) when is_list(exprs) do
|
||||
stack = push_expr_stack(exprs, stack)
|
||||
|
||||
case map_reduce_ok(exprs, context, &fun.(&1, stack, &2)) do
|
||||
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# left ++ right
|
||||
defp of_shared(
|
||||
{{:., _meta1, [:erlang, :++]}, _meta2, [left_expr, right_expr]} = expr,
|
||||
stack,
|
||||
context,
|
||||
fun
|
||||
) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case fun.(left_expr, stack, context) do
|
||||
{:ok, {:list, left}, context} ->
|
||||
case fun.(right_expr, stack, context) do
|
||||
{:ok, {:list, right}, context} ->
|
||||
{:ok, {:list, to_union([left, right], context)}, context}
|
||||
|
||||
{:ok, right, context} ->
|
||||
{:ok, {:list, to_union([left, right], context)}, context}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# {left, right}
|
||||
defp of_shared({left, right}, stack, context, fun) do
|
||||
of_shared({:{}, [], [left, right]}, stack, context, fun)
|
||||
end
|
||||
|
||||
# {...}
|
||||
defp of_shared({:{}, _meta, exprs} = expr, stack, context, fun) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
case map_reduce_ok(exprs, context, &fun.(&1, stack, &2)) do
|
||||
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
# %{...}
|
||||
defp of_shared({:%{}, _meta, args} = expr, stack, context, fun) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
Of.open_map(args, stack, context, fun)
|
||||
end
|
||||
|
||||
# %Struct{...}
|
||||
defp of_shared({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context, fun)
|
||||
when is_atom(module) do
|
||||
stack = push_expr_stack(expr, stack)
|
||||
|
||||
with {:ok, struct, context} <- Of.struct(module, meta1, context),
|
||||
{:ok, map, context} <- Of.open_map(args, stack, context, fun) do
|
||||
unify(map, struct, stack, context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -166,8 +166,11 @@ defmodule Module.Types.Unify do
|
||||
end
|
||||
|
||||
case unify_result do
|
||||
{:ok, var_type, context} ->
|
||||
context = refine_var(var, var_type, stack, context)
|
||||
{:ok, {:var, ^var}, context} ->
|
||||
{:ok, {:var, var}, context}
|
||||
|
||||
{:ok, res_type, context} ->
|
||||
context = refine_var(var, res_type, stack, context)
|
||||
{:ok, {:var, var}, context}
|
||||
|
||||
{:error, reason} ->
|
||||
|
||||
+4
-16
@@ -573,27 +573,15 @@ defmodule Path do
|
||||
@moduledoc false
|
||||
|
||||
def read_link_info(file) do
|
||||
call({:read_link_info, file})
|
||||
:file.read_link_info(file)
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case call({:list_dir, dir}) do
|
||||
{:ok, files} ->
|
||||
{:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
|
||||
other ->
|
||||
other
|
||||
case :file.list_dir(dir) do
|
||||
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, call: 1}
|
||||
|
||||
defp call(tuple) do
|
||||
x = :erlang.dt_spread_tag(true)
|
||||
y = :gen_server.call(:file_server_2, tuple)
|
||||
:erlang.dt_restore_tag(x)
|
||||
y
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -447,12 +447,12 @@ defmodule Record do
|
||||
end
|
||||
end
|
||||
|
||||
defp hoist_expressions(keyword, %{context: nil}) do
|
||||
defp hoist_expressions(keyword, %{context: nil, module: module}) do
|
||||
Enum.map_reduce(keyword, [], fn {key, expr}, acc ->
|
||||
if simple_argument?(expr) do
|
||||
{{key, expr}, acc}
|
||||
else
|
||||
var = Macro.var(key, __MODULE__)
|
||||
var = Macro.unique_var(key, module)
|
||||
{{key, var}, [{:=, [], [var, expr]} | acc]}
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -600,9 +600,17 @@ defmodule System do
|
||||
"""
|
||||
# TODO: Once Erlang/OTP 23 is required, remove conditional, and update @doc accordingly.
|
||||
# The warning is emitted by the compiler - so a @doc annotation is enough
|
||||
# Note Elixir may be compiled in an earlier Erlang version but runs on a
|
||||
# newer one, so we need the check at compilation time and runtime.
|
||||
@doc deprecated: "Use __STACKTRACE__ instead"
|
||||
if function_exported?(:erlang, :get_stacktrace, 0) do
|
||||
def stacktrace, do: apply(:erlang, :get_stacktrace, [])
|
||||
def stacktrace do
|
||||
if function_exported?(:erlang, :get_stacktrace, 0) do
|
||||
apply(:erlang, :get_stacktrace, [])
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
else
|
||||
def stacktrace, do: []
|
||||
end
|
||||
|
||||
@@ -127,11 +127,18 @@ Conveniently, Elixir allows developers to hide modules and functions from the do
|
||||
end
|
||||
end
|
||||
|
||||
However, keep in mind that adding `@doc false` does not make the function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
|
||||
In case you don't want to hide a whole module, you can hide functions individually:
|
||||
|
||||
defmodule MyApp.Sample do
|
||||
@doc false
|
||||
def add(a, b), do: a + b
|
||||
end
|
||||
|
||||
However, keep in mind `@moduledoc false` or `@doc false` do not make a function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
|
||||
|
||||
* Move the undocumented function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. Remember you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
|
||||
|
||||
* Start the function name with one or two underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
|
||||
* Start the function name with one or two underscores, for example, `__add__/2`. Functions starting with underscore are autoamtically treated as hidden, although you can also be explicit and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
|
||||
|
||||
## Code.fetch_docs/1
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generate docs_config.js for version chooser in ExDoc
|
||||
[app] = System.argv()
|
||||
|
||||
{text_tags, 0} = System.cmd("git", ["tag"])
|
||||
skipped = Version.parse!("1.0.3")
|
||||
|
||||
list_contents =
|
||||
text_tags
|
||||
|> String.split()
|
||||
|> Enum.map(fn "v" <> rest -> Version.parse!(rest) end)
|
||||
|> Enum.filter(&(Version.compare(&1, skipped) == :gt))
|
||||
|> Enum.sort({:desc, Version})
|
||||
|> Enum.map_intersperse(", ", fn version ->
|
||||
version_string = to_string(version)
|
||||
~s[{"version":"v#{version_string}", "url":"https://hexdocs.pm/#{app}/#{version_string}"}]
|
||||
end)
|
||||
|
||||
File.mkdir_p!("doc/#{app}")
|
||||
File.write!("doc/#{app}/docs_config.js", ["var versionNodes = [", list_contents, "];\n"])
|
||||
@@ -0,0 +1,12 @@
|
||||
canonical = System.fetch_env!("CANONICAL")
|
||||
|
||||
[
|
||||
deps: [
|
||||
eex: "https://hexdocs.pm/eex/#{canonical}",
|
||||
elixir: "https://hexdocs.pm/elixir/#{canonical}",
|
||||
ex_unit: "https://hexdocs.pm/ex_unit/#{canonical}",
|
||||
iex: "https://hexdocs.pm/iex/#{canonical}",
|
||||
logger: "https://hexdocs.pm/logger/#{canonical}",
|
||||
mix: "https://hexdocs.pm/mix/#{canonical}"
|
||||
]
|
||||
]
|
||||
@@ -319,8 +319,13 @@ build_spec(Meta, _Size, Unit, Type, _Endianness, Sign, Spec, E) when Type == bin
|
||||
build_spec(Meta, Size, Unit, Type, Endianness, Sign, Spec, E) when Type == integer; Type == float ->
|
||||
NumberSize = number_size(Size, Unit),
|
||||
if
|
||||
Type == float, is_integer(NumberSize), NumberSize /= 32, NumberSize /= 64 ->
|
||||
form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize});
|
||||
Type == float, is_integer(NumberSize) ->
|
||||
case valid_float_size(NumberSize) of
|
||||
true ->
|
||||
add_spec(Type, add_spec(Endianness, add_spec(Sign, Spec)));
|
||||
false ->
|
||||
form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize})
|
||||
end;
|
||||
Size == default, Unit /= default ->
|
||||
form_error(Meta, E, ?MODULE, bittype_unit);
|
||||
true ->
|
||||
@@ -331,6 +336,12 @@ number_size(Size, default) when is_integer(Size) -> Size;
|
||||
number_size(Size, Unit) when is_integer(Size) -> Size * Unit;
|
||||
number_size(Size, _) -> Size.
|
||||
|
||||
%% TODO: Simplify when we require OTP 24
|
||||
valid_float_size(16) -> erlang:system_info(otp_release) >= "24";
|
||||
valid_float_size(32) -> true;
|
||||
valid_float_size(64) -> true;
|
||||
valid_float_size(_) -> false.
|
||||
|
||||
add_spec(default, Spec) -> Spec;
|
||||
add_spec(Key, Spec) -> [{Key, [], []} | Spec].
|
||||
|
||||
@@ -372,7 +383,12 @@ format_error(bittype_signed) ->
|
||||
format_error(bittype_unit) ->
|
||||
"integer and float types require a size specifier if the unit specifier is given";
|
||||
format_error({bittype_float_size, Other}) ->
|
||||
io_lib:format("float requires size*unit to be 32 or 64 (default), got: ~p", [Other]);
|
||||
Message =
|
||||
case erlang:system_info(otp_release) >= "24" of
|
||||
true -> "16, 32, or 64";
|
||||
false -> "32 or 64"
|
||||
end,
|
||||
io_lib:format("float requires size*unit to be ~s (default), got: ~p", [Message, Other]);
|
||||
format_error({invalid_literal, Literal}) ->
|
||||
io_lib:format("invalid literal ~ts in <<>>", ['Elixir.Macro':to_string(Literal)]);
|
||||
format_error({undefined_bittype, Expr}) ->
|
||||
|
||||
@@ -94,6 +94,8 @@ handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) ->
|
||||
|
||||
%% Those we implement ourselves
|
||||
handle_file_warning(_, _File, {_Line, v3_core, {map_key_repeated, _}}) -> ok;
|
||||
handle_file_warning(_, _File, {_Line, sys_core_fold, {ignored, useless_building}}) -> ok;
|
||||
%% TODO: remove when we require Erlang/OTP 24
|
||||
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
|
||||
|
||||
%% Ignore all linting errors (only come up on parse transforms)
|
||||
@@ -112,11 +114,11 @@ handle_file_error(File, {Line, Module, Desc}) ->
|
||||
elixir_errors:compile_error([{line, Line}], File, Message).
|
||||
|
||||
%% Mention the capture operator in make_fun
|
||||
custom_format(sys_core_fold, {no_effect, {erlang, make_fun, 3}}) ->
|
||||
custom_format(sys_core_fold, {ignored, {no_effect, {erlang, make_fun, 3}}}) ->
|
||||
"the result of the capture operator & (:erlang.make_fun/3) is never used";
|
||||
|
||||
%% Make no_effect clauses pretty
|
||||
custom_format(sys_core_fold, {no_effect, {erlang, F, A}}) ->
|
||||
custom_format(sys_core_fold, {ignored, {no_effect, {erlang, F, A}}}) ->
|
||||
{Fmt, Args} = case erl_internal:comp_op(F, A) of
|
||||
true -> {"use of operator ~ts has no effect", [elixir_utils:erlang_comparison_op_to_elixir(F)]};
|
||||
false ->
|
||||
@@ -127,16 +129,11 @@ custom_format(sys_core_fold, {no_effect, {erlang, F, A}}) ->
|
||||
end,
|
||||
io_lib:format(Fmt, Args);
|
||||
|
||||
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, and the like
|
||||
custom_format(sys_core_fold, nomatch_guard) ->
|
||||
%% Rewrite nomatch to be more generic, it can happen inside if, unless, and the like
|
||||
custom_format(sys_core_fold, {nomatch, X}) when X == guard; X == no_clause ->
|
||||
"this check/guard will always yield the same result";
|
||||
|
||||
%% Handle literal eval failures
|
||||
custom_format(sys_core_fold, {eval_failure, Error}) ->
|
||||
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
|
||||
["this expression will fail with ", elixir_aliases:inspect(Struct)];
|
||||
|
||||
custom_format(sys_core_fold, {nomatch_shadow,Line,{ErlName,ErlArity}}) ->
|
||||
custom_format(sys_core_fold, {nomatch, {shadow, Line, {ErlName, ErlArity}}}) ->
|
||||
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, ErlArity),
|
||||
|
||||
io_lib:format(
|
||||
@@ -144,6 +141,28 @@ custom_format(sys_core_fold, {nomatch_shadow,Line,{ErlName,ErlArity}}) ->
|
||||
[Name, Arity, Line]
|
||||
);
|
||||
|
||||
%% Handle literal eval failures
|
||||
custom_format(sys_core_fold, {failed, {eval_failure, {Mod, Name, Arity}, Error}}) ->
|
||||
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
|
||||
{ExMod, ExName, ExArgs} = elixir_rewrite:erl_to_ex(Mod, Name, lists:duplicate(Arity, nil)),
|
||||
Call = 'Elixir.Exception':format_mfa(ExMod, ExName, length(ExArgs)),
|
||||
Trimmed = case Call of
|
||||
<<"Kernel.", Rest/binary>> -> Rest;
|
||||
_ -> Call
|
||||
end,
|
||||
["the call to ", Trimmed, " will fail with ", elixir_aliases:inspect(Struct)];
|
||||
|
||||
%% TODO: remove when we require OTP 24
|
||||
custom_format(sys_core_fold, {nomatch_shadow, Line, FA}) ->
|
||||
custom_format(sys_core_fold, {nomatch, {shadow, Line, FA}});
|
||||
custom_format(sys_core_fold, nomatch_guard) ->
|
||||
custom_format(sys_core_fold, {nomatch, guard});
|
||||
custom_format(sys_core_fold, {no_effect, X}) ->
|
||||
custom_format(sys_core_fold, {ignored, {no_effect, X}});
|
||||
custom_format(sys_core_fold, {eval_failure, Error}) ->
|
||||
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
|
||||
["this expression will fail with ", elixir_aliases:inspect(Struct)];
|
||||
|
||||
custom_format([], Desc) ->
|
||||
io_lib:format("~p", [Desc]);
|
||||
|
||||
|
||||
@@ -469,7 +469,7 @@ expand(Other, E) ->
|
||||
escape_env_entries(Meta, #{current_vars := {Read, Write}, unused_vars := {Unused, Version}} = Env0) ->
|
||||
Env1 = case Env0 of
|
||||
#{function := nil} -> Env0;
|
||||
_ -> Env0#{lexical_tracker := nil}
|
||||
_ -> Env0#{lexical_tracker := nil, tracers := []}
|
||||
end,
|
||||
Current = {maybe_escape_map(Read), maybe_escape_map(Write)},
|
||||
Env2 = Env1#{current_vars := Current, unused_vars := {maybe_escape_map(Unused), Version}},
|
||||
|
||||
@@ -139,9 +139,9 @@ compile(Line, Module, Block, Vars, E) ->
|
||||
},
|
||||
|
||||
Binary = elixir_erl:compile(ModuleMap),
|
||||
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
|
||||
autoload_module(Module, Binary, CompileOpts, NE),
|
||||
eval_callbacks(Line, DataBag, after_compile, [NE, Binary], NE),
|
||||
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
|
||||
make_module_available(Module, Binary, ModuleMap),
|
||||
{module, Module, Binary, Result}
|
||||
catch
|
||||
|
||||
@@ -327,6 +327,10 @@ guard_rewrite(Receiver, DotMeta, Right, Meta, Args) ->
|
||||
_ -> {error, {invalid_guard, Receiver, Right, length(Args)}}
|
||||
end.
|
||||
|
||||
%% erlang:is_record/2-3 are compiler guards in Erlang which we
|
||||
%% need to explicitly forbid as they are allowed in erl_internal.
|
||||
allowed_guard(is_record, 2) -> false;
|
||||
allowed_guard(is_record, 3) -> false;
|
||||
allowed_guard(Right, Arity) ->
|
||||
erl_internal:guard_bif(Right, Arity) orelse elixir_utils:guard_op(Right, Arity).
|
||||
|
||||
|
||||
@@ -477,8 +477,8 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H)
|
||||
|
||||
tokenize([$: | String] = Original, Line, Column, Scope, Tokens) ->
|
||||
case tokenize_identifier(String, Line, Column, Scope, false) of
|
||||
{_Kind, Atom, Rest, Length, _Ascii, _Special} ->
|
||||
NewScope = maybe_warn_for_ambiguous_bang_before_equals(atom, Atom, Rest, Scope, Line),
|
||||
{_Kind, Unencoded, Atom, Rest, Length, _Ascii, _Special} ->
|
||||
NewScope = maybe_warn_for_ambiguous_bang_before_equals(atom, Unencoded, Rest, Scope, Line),
|
||||
Token = {atom, {Line, Column, nil}, Atom},
|
||||
tokenize(Rest, Line, Column + 1 + Length, NewScope, [Token | Tokens]);
|
||||
empty ->
|
||||
@@ -559,7 +559,7 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
|
||||
|
||||
tokenize(String, Line, Column, Scope, Tokens) ->
|
||||
case tokenize_identifier(String, Line, Column, Scope, not previous_was_dot(Tokens)) of
|
||||
{Kind, Atom, Rest, Length, Ascii, Special} ->
|
||||
{Kind, Unencoded, Atom, Rest, Length, Ascii, Special} ->
|
||||
HasAt = lists:member($@, Special),
|
||||
|
||||
case Rest of
|
||||
@@ -583,7 +583,7 @@ tokenize(String, Line, Column, Scope, Tokens) ->
|
||||
tokenize_alias(Rest, Line, Column, Atom, Length, Ascii, Special, Scope, Tokens);
|
||||
|
||||
_ when Kind == identifier ->
|
||||
NewScope = maybe_warn_for_ambiguous_bang_before_equals(identifier, Atom, Rest, Scope, Line),
|
||||
NewScope = maybe_warn_for_ambiguous_bang_before_equals(identifier, Unencoded, Rest, Scope, Line),
|
||||
Token = check_call_identifier(Line, Column, Atom, Rest),
|
||||
tokenize(Rest, Line, Column + Length, NewScope, [Token | Tokens]);
|
||||
|
||||
@@ -1142,7 +1142,7 @@ tokenize_identifier(String, Line, Column, Scope, MaybeKeyword) ->
|
||||
{keyword, Atom, Type} ->
|
||||
{keyword, Atom, Type, Rest, Length};
|
||||
{ok, Atom} ->
|
||||
{Kind, Atom, Rest, Length, Ascii, Special};
|
||||
{Kind, Acc, Atom, Rest, Length, Ascii, Special};
|
||||
{error, _Reason} = Error ->
|
||||
Error
|
||||
end;
|
||||
@@ -1462,11 +1462,11 @@ maybe_warn_too_many_of_same_char(_Token, _Rest, _Line, Scope) ->
|
||||
Scope.
|
||||
|
||||
%% TODO: Turn into an error on v2.0
|
||||
maybe_warn_for_ambiguous_bang_before_equals(Kind, Atom, [$= | _], Scope, Line) ->
|
||||
maybe_warn_for_ambiguous_bang_before_equals(Kind, Unencoded, [$= | _], Scope, Line) ->
|
||||
{What, Identifier} =
|
||||
case Kind of
|
||||
atom -> {"atom", [$: | atom_to_list(Atom)]};
|
||||
identifier -> {"identifier", atom_to_list(Atom)}
|
||||
atom -> {"atom", [$: | Unencoded]};
|
||||
identifier -> {"identifier", Unencoded}
|
||||
end,
|
||||
|
||||
case lists:last(Identifier) of
|
||||
|
||||
@@ -224,6 +224,14 @@ defmodule CodeTest do
|
||||
Code.string_to_quoted("there_is_no_such_var", static_atoms_encoder: encoder)
|
||||
end
|
||||
|
||||
test "static_atoms_encoder ambiguities" do
|
||||
encoder = fn string, _meta -> {:ok, {:atom, string}} end
|
||||
|
||||
# We check a=1 for precedence issues with a!=1, make sure it works
|
||||
assert Code.string_to_quoted!("a = 1", static_atoms_encoder: encoder)
|
||||
assert Code.string_to_quoted!("a=1", static_atoms_encoder: encoder)
|
||||
end
|
||||
|
||||
test "static_atoms_encoder does not encode keywords" do
|
||||
encoder = fn atom, _meta -> raise "shouldn't be invoked for #{atom}" end
|
||||
|
||||
|
||||
@@ -116,6 +116,17 @@ defmodule EnumTest do
|
||||
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
|
||||
|
||||
assert Enum.chunk_while([5, 7, 9, 11], [], chunk_fun, after_fun) == [[5, 7, 9]]
|
||||
|
||||
assert Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun) == [[1, 2], [3, 5, 7]]
|
||||
|
||||
chunk_fn2 = fn
|
||||
-1, acc -> {:cont, acc, 0}
|
||||
i, acc -> {:cont, acc + i}
|
||||
end
|
||||
|
||||
after_fn2 = fn acc -> {:cont, acc, 0} end
|
||||
|
||||
assert Enum.chunk_while([1, -1, 2, 3, -1, 4, 5, 6], 0, chunk_fn2, after_fn2) == [1, 5, 15]
|
||||
end
|
||||
|
||||
test "concat/1" do
|
||||
|
||||
@@ -446,11 +446,12 @@ defmodule ExceptionTest do
|
||||
* min/1
|
||||
"""
|
||||
|
||||
assert blame_message(:erlang, & &1.gt_cookie()) == """
|
||||
function :erlang.gt_cookie/0 is undefined or private. Did you mean one of:
|
||||
assert blame_message(:erlang, & &1.hal()) == """
|
||||
function :erlang.hal/0 is undefined or private. Did you mean one of:
|
||||
|
||||
* get_cookie/0
|
||||
* set_cookie/2
|
||||
* halt/0
|
||||
* halt/1
|
||||
* halt/2
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
@@ -192,6 +192,11 @@ defmodule IO.ANSI.DocsTest do
|
||||
assert result == " • one\n • two\n • three\n\e[0m"
|
||||
end
|
||||
|
||||
test "* list is converted without ansi" do
|
||||
result = format_markdown("* one\n* two\n* three\n", enabled: false)
|
||||
assert result == " * one\n * two\n * three"
|
||||
end
|
||||
|
||||
test "* list surrounded by text is converted" do
|
||||
result = format_markdown("Count:\n\n* one\n* two\n* three\n\nDone")
|
||||
assert result == "Count:\n\e[0m\n • one\n • two\n • three\n\e[0m\nDone\n\e[0m"
|
||||
|
||||
@@ -692,16 +692,6 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "match attribute in module" do
|
||||
msg = "invalid write attribute syntax, you probably meant to use: @foo expression"
|
||||
|
||||
assert_raise ArgumentError, msg, fn ->
|
||||
defmodule MatchAttributeInModule do
|
||||
@foo = 42
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid case clauses" do
|
||||
assert_eval_raise CompileError,
|
||||
"nofile:1: expected one argument for :do clauses (->) in \"case\"",
|
||||
@@ -828,7 +818,7 @@ defmodule Kernel.ErrorsTest do
|
||||
"nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
|
||||
'''
|
||||
defmodule Kernel.ErrorsTest.FunctionImportConflict do
|
||||
import :erlang, warn: false
|
||||
import :erlang, only: [exit: 1], warn: false
|
||||
def foo, do: exit(:test)
|
||||
end
|
||||
'''
|
||||
@@ -1202,7 +1192,7 @@ defmodule Kernel.ErrorsTest do
|
||||
rescue
|
||||
ArgumentError ->
|
||||
assert [
|
||||
{:erlang, :apply, [1, :foo, []], []},
|
||||
{:erlang, :apply, [1, :foo, []], _},
|
||||
{__MODULE__, :bad_remote_call, 1, [file: _, line: _]} | _
|
||||
] = __STACKTRACE__
|
||||
end
|
||||
|
||||
@@ -294,6 +294,7 @@ defmodule Kernel.ExpansionTest do
|
||||
test "__ENV__" do
|
||||
env = %{__ENV__ | line: 0}
|
||||
assert expand_env(quote(do: __ENV__), env) == {Macro.escape(env), env}
|
||||
assert %{lexical_tracker: nil, tracers: []} = __ENV__
|
||||
end
|
||||
|
||||
test "__ENV__.accessor" do
|
||||
@@ -302,6 +303,9 @@ defmodule Kernel.ExpansionTest do
|
||||
|
||||
assert expand_env(quote(do: __ENV__.unknown), env) ==
|
||||
{quote(do: unquote(Macro.escape(env)).unknown), env}
|
||||
|
||||
assert __ENV__.lexical_tracker == nil
|
||||
assert __ENV__.tracers == []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2383,17 +2387,49 @@ defmodule Kernel.ExpansionTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "raises for invalid size * unit for floats" do
|
||||
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128"
|
||||
# TODO: Simplify when we require OTP 24
|
||||
if System.otp_release() >= "24" do
|
||||
test "16-bit floats" do
|
||||
import Kernel, except: [-: 2]
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::32*4>>))
|
||||
assert expand(quote(do: <<12.3::float-16>>)) |> clean_meta([:alignment]) ==
|
||||
quote(do: <<12.3::float()-size(16)>>)
|
||||
end
|
||||
|
||||
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256"
|
||||
test "raises for invalid size * unit for floats" do
|
||||
message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 128"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::256>>))
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::32*4>>))
|
||||
end
|
||||
|
||||
message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 256"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::256>>))
|
||||
end
|
||||
end
|
||||
else
|
||||
test "16-bit floats" do
|
||||
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 16"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::16>>))
|
||||
end
|
||||
end
|
||||
|
||||
test "raises for invalid size * unit for floats" do
|
||||
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::32*4>>))
|
||||
end
|
||||
|
||||
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<12.3::256>>))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -310,6 +310,22 @@ defmodule Kernel.GuardTest do
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise CompileError,
|
||||
~r"cannot invoke remote function :erlang\.is_record/2 inside guards",
|
||||
fn ->
|
||||
defmodule IsRecord2Usage do
|
||||
defguard foo(rec) when :erlang.is_record(rec, :tag)
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise CompileError,
|
||||
~r"cannot invoke remote function :erlang\.is_record/3 inside guards",
|
||||
fn ->
|
||||
defmodule IsRecord3Usage do
|
||||
defguard foo(rec) when :erlang.is_record(rec, :tag, 7)
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise CompileError,
|
||||
~r"cannot invoke remote function :erlang\.\+\+/2 inside guards",
|
||||
fn ->
|
||||
|
||||
@@ -931,26 +931,33 @@ defmodule Kernel.WarningTest do
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
# TODO: Simplify when we require OTP 24
|
||||
if System.otp_release() >= "24" do
|
||||
@argument_error_message "the call to :erlang.atom_to_binary/2"
|
||||
@arithmetic_error_message "the call to +/2"
|
||||
else
|
||||
@argument_error_message "this expression"
|
||||
@arithmetic_error_message "this expression"
|
||||
end
|
||||
|
||||
test "eval failure warning" do
|
||||
assert capture_err(fn ->
|
||||
assert_raise ArgumentError, fn ->
|
||||
Code.eval_string("""
|
||||
defmodule Sample do
|
||||
Atom.to_string "abc"
|
||||
end
|
||||
""")
|
||||
Code.eval_string("""
|
||||
defmodule Sample1 do
|
||||
def foo, do: Atom.to_string "abc"
|
||||
end
|
||||
end) =~ ~r"this expression will fail with ArgumentError\n.*nofile:2"
|
||||
""")
|
||||
end) =~ "#{@argument_error_message} will fail with ArgumentError\n nofile:2"
|
||||
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string("""
|
||||
defmodule Sample do
|
||||
defmodule Sample2 do
|
||||
def foo, do: 1 + nil
|
||||
end
|
||||
""")
|
||||
end) =~ ~r"this expression will fail with ArithmeticError\n.*nofile:2"
|
||||
end) =~ "#{@arithmetic_error_message} will fail with ArithmeticError\n nofile:2"
|
||||
after
|
||||
purge([Sample])
|
||||
purge([Sample1, Sample2])
|
||||
end
|
||||
|
||||
test "undefined function for behaviour" do
|
||||
@@ -1147,31 +1154,6 @@ defmodule Kernel.WarningTest do
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "with and do clauses emit errors, else clauses do not" do
|
||||
assert capture_err(fn ->
|
||||
Code.compile_string("""
|
||||
with {:first, int} when is_integer(int) <- {:second, Integer.gcd(2, 4)} do
|
||||
int
|
||||
end
|
||||
""")
|
||||
end) =~ "this clause cannot match"
|
||||
|
||||
assert capture_err(fn ->
|
||||
Code.compile_string("""
|
||||
with {:first, int1} when is_integer(int1) <- {:first, Integer.gcd(2, 4)},
|
||||
{:second, int2} when is_integer(int2) <- {:second, Integer.gcd(2, 4)} do
|
||||
{:ok, int1 + int2}
|
||||
else
|
||||
{:first, nil} -> {:error, "first number is not integer"}
|
||||
{:second, nil} -> {:error, "second number is not integer"}
|
||||
end
|
||||
""")
|
||||
end) == ""
|
||||
after
|
||||
purge(Sample1)
|
||||
purge(Sample2)
|
||||
end
|
||||
|
||||
test "warning on code point escape" do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string("? ")
|
||||
|
||||
@@ -592,16 +592,6 @@ defmodule KernelTest do
|
||||
_ = a
|
||||
end
|
||||
|
||||
test "setting attribute with uppercase" do
|
||||
message = "module attributes set via @ cannot start with an uppercase letter"
|
||||
|
||||
assert_raise ArgumentError, message, fn ->
|
||||
defmodule UpcaseAttrSample do
|
||||
@Upper
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "in module body" do
|
||||
defmodule InSample do
|
||||
@foo [:a, :b]
|
||||
@@ -782,6 +772,40 @@ defmodule KernelTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "@" do
|
||||
test "setting attribute with do-block" do
|
||||
exception =
|
||||
catch_error(
|
||||
defmodule UpcaseAttrSample do
|
||||
@foo quote do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
assert exception.message =~ "expected 0 or 1 argument for @foo, got 2"
|
||||
assert exception.message =~ "You probably want to wrap the argument value in parentheses"
|
||||
end
|
||||
|
||||
test "setting attribute with uppercase" do
|
||||
message = "module attributes set via @ cannot start with an uppercase letter"
|
||||
|
||||
assert_raise ArgumentError, message, fn ->
|
||||
defmodule UpcaseAttrSample do
|
||||
@Upper
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "matching attribute" do
|
||||
assert_raise ArgumentError, ~r"invalid write attribute syntax", fn ->
|
||||
defmodule MatchAttributeInModule do
|
||||
@foo = 42
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "defdelegate" do
|
||||
defdelegate my_flatten(list), to: List, as: :flatten
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file("../../test_helper.exs", __DIR__)
|
||||
Code.require_file("type_helper.exs", __DIR__)
|
||||
|
||||
defmodule Module.Types.IntegrationTest do
|
||||
use ExUnit.Case
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file("../../test_helper.exs", __DIR__)
|
||||
Code.require_file("type_helper.exs", __DIR__)
|
||||
|
||||
defmodule Module.Types.PatternTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file("../../test_helper.exs", __DIR__)
|
||||
Code.require_file("type_helper.exs", __DIR__)
|
||||
|
||||
defmodule Module.Types.TypesTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -13,23 +13,21 @@ defmodule Module.Types.TypesTest do
|
||||
expr = TypeHelper.expand_expr(patterns, guards, body, __CALLER__)
|
||||
|
||||
quote do
|
||||
unquote(Macro.escape(expr))
|
||||
|> Module.Types.TypesTest.__expr__()
|
||||
|> to_warning()
|
||||
Module.Types.TypesTest.__expr__(unquote(Macro.escape(expr)))
|
||||
end
|
||||
end
|
||||
|
||||
def __expr__({patterns, guards, body}) do
|
||||
with {:ok, _types, context} <-
|
||||
Pattern.of_head(patterns, guards, TypeHelper.new_stack(), TypeHelper.new_context()),
|
||||
{:ok, type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
|
||||
{:ok, _type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
|
||||
case context.warnings do
|
||||
[warning] -> {:warning, warning}
|
||||
_ -> flunk("expexted error, got: #{inspect(Types.lift_type(type, context))}")
|
||||
[warning] -> to_message(:warning, warning)
|
||||
_ -> :none
|
||||
end
|
||||
else
|
||||
{:error, {type, reason, context}} ->
|
||||
{:error, {type, reason, context}}
|
||||
to_message(:error, {type, reason, context})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,13 +49,13 @@ defmodule Module.Types.TypesTest do
|
||||
min
|
||||
end
|
||||
|
||||
defp to_warning({:warning, {module, warning, _location}}) do
|
||||
defp to_message(:warning, {module, warning, _location}) do
|
||||
warning
|
||||
|> module.format_warning()
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
defp to_warning({:error, {type, reason, context}}) do
|
||||
defp to_message(:error, {type, reason, context}) do
|
||||
{Module.Types, error, _location} = Module.Types.error_to_warning(type, reason, context)
|
||||
|
||||
error
|
||||
@@ -472,4 +470,20 @@ defmodule Module.Types.TypesTest do
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
describe "regressions" do
|
||||
test "recursive map fields" do
|
||||
assert warning(
|
||||
[queried],
|
||||
with(
|
||||
true <- is_nil(queried.foo.bar),
|
||||
_ = queried.foo
|
||||
) do
|
||||
%{foo: %{other_id: _other_id} = foo} = queried
|
||||
%{other_id: id} = foo
|
||||
%{id: id}
|
||||
end
|
||||
) == :none
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Code.require_file("../../test_helper.exs", __DIR__)
|
||||
Code.require_file("type_helper.exs", __DIR__)
|
||||
|
||||
defmodule Module.Types.UnifyTest do
|
||||
use ExUnit.Case, async: true
|
||||
@@ -350,11 +350,22 @@ defmodule Module.Types.UnifyTest do
|
||||
assert {{:var, 2}, var_context} = new_var({:baz, [version: 2], nil}, var_context)
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
assert {:ok, {:var, _}, _context} = unify({:var, 1}, {:var, 0}, context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
|
||||
assert context.types[0] == {:var, 1}
|
||||
assert context.types[1] == {:var, 0}
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, :tuple, var_context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, context)
|
||||
assert context.types[0] == {:var, 1}
|
||||
assert context.types[1] == :tuple
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 2}, context)
|
||||
assert {:ok, {:var, _}, _context} = unify({:var, 2}, {:var, 0}, context)
|
||||
assert context.types[0] == :unbound
|
||||
assert context.types[1] == {:var, 0}
|
||||
assert context.types[2] == {:var, 1}
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
|
||||
|
||||
@@ -95,17 +95,16 @@ defmodule ModuleTest do
|
||||
end
|
||||
|
||||
test "supports read access to module from __after_compile__/2" do
|
||||
contents =
|
||||
quote do
|
||||
@after_compile __MODULE__
|
||||
@foo 42
|
||||
defmodule ModuleTest.NoRaise do
|
||||
@after_compile __MODULE__
|
||||
@foo 42
|
||||
|
||||
def __after_compile__(%Macro.Env{module: module}, bin) when is_binary(bin) do
|
||||
Module.get_attribute(module, :foo)
|
||||
end
|
||||
def __after_compile__(%Macro.Env{module: module}, bin) when is_binary(bin) do
|
||||
send(self(), Module.get_attribute(module, :foo))
|
||||
end
|
||||
end
|
||||
|
||||
Module.create(ModuleTest.NoRaise, contents, __ENV__)
|
||||
assert_received 42
|
||||
end
|
||||
|
||||
test "in memory modules are tagged as so" do
|
||||
|
||||
@@ -156,7 +156,20 @@ defmodule RecordTest do
|
||||
)
|
||||
|
||||
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
|
||||
assert Process.info(self(), :messages) == {:messages, [:inner_age, :inner_name, :name]}
|
||||
assert for(_ <- 1..3, do: assert_receive(_)) == [:inner_age, :inner_name, :name]
|
||||
|
||||
user =
|
||||
user(
|
||||
name: send(self(), :name),
|
||||
age:
|
||||
user(
|
||||
age: send(self(), :inner_age),
|
||||
name: send(self(), :inner_name)
|
||||
)
|
||||
)
|
||||
|
||||
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
|
||||
assert for(_ <- 1..3, do: assert_receive(_)) == [:name, :inner_age, :inner_name]
|
||||
end
|
||||
|
||||
Record.defrecord(
|
||||
|
||||
@@ -70,8 +70,14 @@ defmodule ExUnit.Diff do
|
||||
diff_quoted(number, right, env)
|
||||
end
|
||||
|
||||
defp diff_quoted({:++, _, _} = left, right, env) when is_list(right) do
|
||||
diff_maybe_improper_list(left, right, env)
|
||||
defp diff_quoted({:++, meta, [prefix, suffix]}, right, env) when is_list(right) do
|
||||
case prefix do
|
||||
{_, [expanded: expanded] ++ _, _} ->
|
||||
diff_maybe_improper_list({:++, meta, [expanded, suffix]}, right, env)
|
||||
|
||||
_ ->
|
||||
diff_maybe_improper_list({:++, meta, [prefix, suffix]}, right, env)
|
||||
end
|
||||
end
|
||||
|
||||
defp diff_quoted({:{}, _, left}, right, env) when is_tuple(right) do
|
||||
|
||||
@@ -345,7 +345,6 @@ defmodule ExUnit.DiffTest do
|
||||
|
||||
refute_diff([:a, :b] ++ [:c] = [:a, :b], "[:a, :b] ++ [-:c-]", "[:a, :b]")
|
||||
refute_diff([:a, :c] ++ [:b] = [:a, :b], "[:a, -:c-] ++ [:b]", "[:a, :b]")
|
||||
|
||||
refute_diff([:a] ++ [:b] ++ [:c] = [:a, :b], "[:a] ++ [:b] ++ [-:c-]", "[:a, :b]")
|
||||
|
||||
assert_diff([:a] ++ :b = [:a | :b], [])
|
||||
@@ -353,10 +352,16 @@ defmodule ExUnit.DiffTest do
|
||||
|
||||
refute_diff([:a, :b] ++ :c = [:a, :b, :c], "[:a, :b] ++ -:c-", "[:a, :b, +:c+]")
|
||||
refute_diff([:a] ++ [:b] ++ :c = [:a, :b, :c], "[:a] ++ [:b] ++ -:c-", "[:a, :b, +:c+]")
|
||||
|
||||
refute_diff([:a] ++ [:b] = :a, "-[:a] ++ [:b]-", "+:a+")
|
||||
end
|
||||
|
||||
@a [:a]
|
||||
test "concat lists with module attributes" do
|
||||
assert_diff(@a ++ [:b] = [:a, :b], [])
|
||||
refute_diff(@a ++ [:b] = [:a], "[:a] ++ [-:b-]", "[:a]")
|
||||
refute_diff(@a ++ [:b] = [:b], "[-:a-] ++ [:b]", "[:b]")
|
||||
end
|
||||
|
||||
test "mixed lists" do
|
||||
refute_diff([:a | :b] = [:a, :b], "[:a | -:b-]", "[:a, +:b+]")
|
||||
refute_diff([:a, :b] = [:a | :b], "[:a, -:b-]", "[:a | +:b+]")
|
||||
|
||||
@@ -357,15 +357,22 @@ defmodule Logger.Translator do
|
||||
[]
|
||||
end
|
||||
|
||||
defp child_debug(:debug, restart_type: restart, shutdown: shutdown, child_type: type) do
|
||||
["\nRestart: ", inspect(restart), "\nShutdown: ", inspect(shutdown)] ++
|
||||
["\nType: ", inspect(type)]
|
||||
defp child_debug(:debug, opts) do
|
||||
for {key, value} <- opts do
|
||||
child_debug_key(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
defp child_debug(_min_level, _child) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp child_debug_key(:restart_type, value), do: ["\nRestart: " | inspect(value)]
|
||||
defp child_debug_key(:shutdown, value), do: ["\nShutdown: " | inspect(value)]
|
||||
defp child_debug_key(:child_type, value), do: ["\nType: " | inspect(value)]
|
||||
defp child_debug_key(:significant, value), do: if(value, do: "\nSignificant: true", else: [])
|
||||
defp child_debug_key(_, _), do: []
|
||||
|
||||
defp report_crash(min_level, [[{:initial_call, initial_call} | crashed], linked]) do
|
||||
mfa = initial_call_to_mfa(initial_call)
|
||||
report_crash(min_level, crashed, [{:initial_call, mfa}], linked)
|
||||
|
||||
@@ -25,6 +25,12 @@ defmodule Mix.Compilers.ApplicationTracer do
|
||||
:ok
|
||||
end
|
||||
|
||||
# Also skip __impl__ calls inside protocols as they are meant
|
||||
# to invert dependencies.
|
||||
def trace({_, _, _, :__impl__, _}, _env) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def trace({type, meta, module, function, arity}, env)
|
||||
when type in [:remote_function, :remote_macro, :imported_function, :imported_macro] do
|
||||
# Unknown modules need to be looked up and filtered later
|
||||
@@ -110,8 +116,8 @@ defmodule Mix.Compilers.ApplicationTracer do
|
||||
def format_warning({:undefined_app, app, module, function, arity}) do
|
||||
"""
|
||||
#{Exception.format_mfa(module, function, arity)} defined in application :#{app} \
|
||||
is used by the current application but the current application does not directly \
|
||||
depend on :#{app}. To fix this, you must do one of:
|
||||
is used by the current application but the current application does not depend \
|
||||
on :#{app}. To fix this, you must do one of:
|
||||
|
||||
1. If :#{app} is part of Erlang/Elixir, you must include it under \
|
||||
:extra_applications inside "def application" in your mix.exs
|
||||
@@ -120,7 +126,7 @@ defmodule Mix.Compilers.ApplicationTracer do
|
||||
in your mix.exs
|
||||
|
||||
3. In case you don't want to add a requirement to :#{app}, you may \
|
||||
optionally skip this warning by adding [xref: [exclude: #{inspect(module)}]] \
|
||||
optionally skip this warning by adding [xref: [exclude: [#{inspect(module)}]]] \
|
||||
to your "def project" in mix.exs
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -271,7 +271,7 @@ defmodule Mix.Compilers.Erlang do
|
||||
defp to_diagnostics(warnings_or_errors, severity) do
|
||||
for {file, issues} <- warnings_or_errors,
|
||||
{line, module, data} <- issues do
|
||||
position = if is_integer(line) and line >= 1, do: line
|
||||
position = line(line)
|
||||
|
||||
%Mix.Task.Compiler.Diagnostic{
|
||||
file: Path.absname(file),
|
||||
@@ -288,7 +288,12 @@ defmodule Mix.Compilers.Erlang do
|
||||
for {_, warnings} <- entries,
|
||||
{file, issues} <- warnings,
|
||||
{line, module, message} <- issues do
|
||||
IO.puts("#{file}:#{line}: Warning: #{module.format_error(message)}")
|
||||
IO.puts("#{file}:#{line(line)}: Warning: #{module.format_error(message)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp line({line, _column}) when is_integer(line) and line >= 1, do: line
|
||||
# TODO: remove when we require OTP 24
|
||||
defp line(line) when is_integer(line) and line >= 1, do: line
|
||||
defp line(_), do: nil
|
||||
end
|
||||
|
||||
@@ -176,7 +176,11 @@ defmodule Mix.Config do
|
||||
"""
|
||||
@doc deprecated: "Use Config.Reader.read_imports!/2 instead"
|
||||
def eval!(file, imported_paths \\ []) do
|
||||
Config.Reader.read_imports!(file, imports: imported_paths)
|
||||
Config.Reader.read_imports!(file,
|
||||
imports: imported_paths,
|
||||
env: Mix.env(),
|
||||
target: Mix.target()
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -195,7 +199,7 @@ defmodule Mix.Config do
|
||||
@doc deprecated: "Use Config.Reader.read!/2 instead"
|
||||
@spec read!(Path.t(), [Path.t()]) :: keyword
|
||||
def read!(file, imported_paths \\ []) do
|
||||
Config.Reader.read!(file, imports: imported_paths)
|
||||
Config.Reader.read!(file, imports: imported_paths, env: Mix.env(), target: Mix.target())
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule Mix.Local do
|
||||
def path_for(:escript), do: Mix.path_for(:escripts)
|
||||
|
||||
@doc """
|
||||
Appends archives paths into Erlang code path.
|
||||
Appends archive paths to the Erlang code path.
|
||||
"""
|
||||
def append_archives do
|
||||
for archive <- archives_ebins() do
|
||||
@@ -48,7 +48,18 @@ defmodule Mix.Local do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends Mix paths into Erlang code path.
|
||||
Removes archive paths from Erlang code path.
|
||||
"""
|
||||
def remove_archives do
|
||||
for archive <- archives_ebins() do
|
||||
Code.delete_path(archive)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Appends Mix paths to the Erlang code path.
|
||||
"""
|
||||
def append_paths do
|
||||
Enum.each(mix_paths(), &Code.append_path(&1))
|
||||
|
||||
@@ -808,11 +808,7 @@ defmodule Mix.Release do
|
||||
{:ok, {_, chunks}} ->
|
||||
chunks = for {name, chunk} <- chunks, is_binary(chunk), do: {name, chunk}
|
||||
{:ok, binary} = :beam_lib.build_module(chunks)
|
||||
{:ok, fd} = :ram_file.open(binary, [:write, :binary])
|
||||
{:ok, _} = :ram_file.compress(fd)
|
||||
{:ok, binary} = :ram_file.get_file(fd)
|
||||
:ok = :ram_file.close(fd)
|
||||
{:ok, binary}
|
||||
{:ok, :zlib.gzip(binary)}
|
||||
|
||||
{:error, _, _} = error ->
|
||||
error
|
||||
|
||||
+26
-16
@@ -355,12 +355,12 @@ defmodule Mix.Task do
|
||||
alias = Mix.Project.config()[:aliases][String.to_atom(task)]
|
||||
|
||||
cond do
|
||||
is_nil(alias) ->
|
||||
run_task(proj, task, args)
|
||||
|
||||
Mix.TasksServer.run({:alias, task, proj}) ->
|
||||
alias && Mix.TasksServer.run({:alias, task, proj}) ->
|
||||
run_alias(List.wrap(alias), args, proj, task, :ok)
|
||||
|
||||
!Mix.TasksServer.get({:task, task, proj}) ->
|
||||
run_task(proj, task, args)
|
||||
|
||||
true ->
|
||||
:noop
|
||||
end
|
||||
@@ -371,8 +371,8 @@ defmodule Mix.Task do
|
||||
# 2. Otherwise we compile and load dependencies
|
||||
# 3. Finally, we compile the current project in hope it is available.
|
||||
module =
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("compile", []) end) ||
|
||||
get_task_or_run(proj, task, fn -> run("deps.loadpaths") end) ||
|
||||
get_task_or_run(proj, task, fn -> run("compile", []) end) ||
|
||||
get!(task)
|
||||
|
||||
recursive = recursive(module)
|
||||
@@ -387,15 +387,16 @@ defmodule Mix.Task do
|
||||
Mix.ProjectStack.on_recursing_root(fn -> run(task, args) end)
|
||||
|
||||
Mix.TasksServer.run({:task, task, proj}) ->
|
||||
if Mix.debug?(), do: output_task_debug_info(task, args, proj)
|
||||
run_requirements(module)
|
||||
|
||||
try do
|
||||
module.run(args)
|
||||
rescue
|
||||
e in OptionParser.ParseError ->
|
||||
Mix.raise("Could not invoke task #{inspect(task)}: " <> Exception.message(e))
|
||||
end
|
||||
with_debug(task, args, proj, fn ->
|
||||
try do
|
||||
module.run(args)
|
||||
rescue
|
||||
e in OptionParser.ParseError ->
|
||||
Mix.raise("Could not invoke task #{inspect(task)}: " <> Exception.message(e))
|
||||
end
|
||||
end)
|
||||
|
||||
true ->
|
||||
:noop
|
||||
@@ -409,8 +410,16 @@ defmodule Mix.Task do
|
||||
end)
|
||||
end
|
||||
|
||||
defp output_task_debug_info(task, args, proj) do
|
||||
Mix.shell().info("** Running mix " <> task_to_string(task, args) <> project_to_string(proj))
|
||||
defp with_debug(task, args, proj, fun) do
|
||||
if Mix.debug?() do
|
||||
shell = Mix.shell()
|
||||
shell.info(["-> Running mix ", task_to_string(task, args), project_to_string(proj)])
|
||||
{time, res} = :timer.tc(fun)
|
||||
shell.info(["<- Ran mix ", task, " in ", Integer.to_string(div(time, 1000)), "ms"])
|
||||
res
|
||||
else
|
||||
fun.()
|
||||
end
|
||||
end
|
||||
|
||||
defp project_to_string(nil), do: ""
|
||||
@@ -450,7 +459,8 @@ defmodule Mix.Task do
|
||||
run_alias(t, alias_args, proj, original_task, res)
|
||||
end
|
||||
|
||||
defp run_alias([], _alias_task, _proj, _original_task, res) do
|
||||
defp run_alias([], _alias_args, proj, original_task, res) do
|
||||
Mix.TasksServer.put({:task, original_task, proj})
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
@@ -103,12 +103,13 @@ defmodule Mix.Tasks.Compile.Elixir do
|
||||
manifest = manifest()
|
||||
configs = [Mix.Project.config_mtime() | Mix.Tasks.Compile.Erlang.manifests()]
|
||||
force = opts[:force] || Mix.Utils.stale?(configs, [manifest])
|
||||
{tracers, opts} = pop_tracers(opts)
|
||||
|
||||
opts =
|
||||
(project[:elixirc_options] || [])
|
||||
|> Keyword.merge(opts)
|
||||
|> xref_exclude_opts(project)
|
||||
|> tracers_opts()
|
||||
|> tracers_opts(tracers)
|
||||
|> profile_opts()
|
||||
|
||||
Mix.Compilers.Elixir.compile(manifest, srcs, dest, [:ex], force, opts)
|
||||
@@ -134,17 +135,21 @@ defmodule Mix.Tasks.Compile.Elixir do
|
||||
end
|
||||
end
|
||||
|
||||
defp tracers_opts(opts) do
|
||||
defp pop_tracers(opts) do
|
||||
case Keyword.pop_values(opts, :tracer) do
|
||||
{[], opts} ->
|
||||
opts
|
||||
{[], opts}
|
||||
|
||||
{tracers, opts} ->
|
||||
tracers = Enum.map(tracers, &Module.concat([&1]))
|
||||
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
|
||||
{Enum.map(tracers, &Module.concat([&1])), opts}
|
||||
end
|
||||
end
|
||||
|
||||
defp tracers_opts(opts, tracers) do
|
||||
tracers = tracers ++ Code.get_compiler_option(:tracers)
|
||||
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
|
||||
end
|
||||
|
||||
defp profile_opts(opts) do
|
||||
case Keyword.fetch(opts, :profile) do
|
||||
{:ok, "time"} -> Keyword.put(opts, :profile, :time)
|
||||
|
||||
@@ -100,9 +100,10 @@ defmodule Mix.Tasks.Compile.Erlang do
|
||||
file = Erlang.to_erl_file(Path.rootname(input, ".erl"))
|
||||
|
||||
case :compile.file(file, erlc_options) do
|
||||
{:error, :badarg} ->
|
||||
# TODO: Don't handle {:error, :badarg} when we require OTP 24
|
||||
error when error == :error or error == {:error, :badarg} ->
|
||||
message =
|
||||
"Compiling Erlang #{inspect(file)} failed with ArgumentError, probably because of invalid :erlc_options"
|
||||
"Compiling Erlang file #{inspect(file)} failed, probably because of invalid :erlc_options"
|
||||
|
||||
Mix.raise(message)
|
||||
|
||||
|
||||
@@ -197,14 +197,8 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
config_path = Path.join(dep_path, "mix.rebar.config")
|
||||
lib_path = Path.join(config[:env_path], "lib/*/ebin")
|
||||
|
||||
# REBAR_BARE_COMPILER_OUTPUT_DIR is only honored by rebar3 >= 3.14
|
||||
env = [
|
||||
{"REBAR_CONFIG", config_path},
|
||||
{"TERM", "dumb"},
|
||||
{"REBAR_BARE_COMPILER_OUTPUT_DIR", dep_path}
|
||||
]
|
||||
|
||||
cmd = "#{rebar_cmd(dep)} bare compile --paths #{lib_path}"
|
||||
env = [{"REBAR_CONFIG", config_path}, {"TERM", "dumb"}]
|
||||
cmd = "#{rebar_cmd(dep)} bare compile --paths=#{inspect(lib_path)}"
|
||||
|
||||
File.mkdir_p!(dep_path)
|
||||
File.write!(config_path, rebar_config(dep))
|
||||
|
||||
@@ -252,7 +252,8 @@ defmodule Mix.Tasks.Xref do
|
||||
{:ok, {_, [debug_info: debug_info]}} <- :beam_lib.chunks(path, [:debug_info]),
|
||||
{:debug_info_v1, backend, data} <- debug_info,
|
||||
{:ok, %{definitions: defs}} <- backend.debug_info(:elixir_v1, module, data, []),
|
||||
do: walk_definitions(module, source, defs)
|
||||
do: walk_definitions(module, source, defs),
|
||||
else: (_ -> [])
|
||||
end
|
||||
|
||||
defp walk_definitions(module, file, definitions) do
|
||||
|
||||
@@ -21,6 +21,10 @@ defmodule Mix.TasksServer do
|
||||
Agent.update(@name, &Map.put(&1, tuple, true), @timeout)
|
||||
end
|
||||
|
||||
def get(tuple) do
|
||||
Agent.get(@name, &Map.get(&1, tuple), @timeout)
|
||||
end
|
||||
|
||||
def delete_many(many) do
|
||||
Agent.update(@name, &Map.drop(&1, many), @timeout)
|
||||
end
|
||||
|
||||
@@ -10,11 +10,14 @@ defmodule Mix.AliasesTest do
|
||||
h: "hello",
|
||||
p: &inspect/1,
|
||||
compile: "hello",
|
||||
cmd: &call_cmd/1,
|
||||
help: ["help", "hello"],
|
||||
"nested.h": [&Mix.shell().info(inspect(&1)), "h foo bar"]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
defp call_cmd(args), do: Mix.Task.run("cmd", args)
|
||||
end
|
||||
|
||||
setup do
|
||||
@@ -50,13 +53,18 @@ defmodule Mix.AliasesTest do
|
||||
assert Mix.Task.run("compile", []) == :noop
|
||||
end
|
||||
|
||||
test "run alias override with recursion" do
|
||||
test "run alias override with name-recursion" do
|
||||
assert Mix.Task.rerun("help", []) == "Hello, World!"
|
||||
assert_received {:mix_shell, :info, ["mix test" <> _]}
|
||||
|
||||
# Arguments are passed to the recursive task and not the last one.
|
||||
assert ExUnit.CaptureIO.capture_io(fn ->
|
||||
Mix.Task.rerun("help", ["test"]) == "Hello, World!"
|
||||
Mix.Task.rerun("help", ["test"])
|
||||
end) =~ "mix test"
|
||||
end
|
||||
|
||||
test "run alias override with code-recursion" do
|
||||
assert Mix.Task.rerun("cmd", ["echo", "hello"]) == :ok
|
||||
assert_received {:mix_shell, :run, ["hello" <> _]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,11 +60,11 @@ defmodule Mix.CLITest do
|
||||
assert contents =~ "This won't appear"
|
||||
|
||||
contents = mix(~w[my_hello], [{"MIX_DEBUG", "1"}])
|
||||
assert contents =~ "** Running mix my_hello (inside MyProject)"
|
||||
assert contents =~ "-> Running mix my_hello (inside MyProject)"
|
||||
assert contents =~ "** (Mix.Error) oops"
|
||||
|
||||
contents = mix(~w[my_hello], [{"MIX_DEBUG", "0"}])
|
||||
refute contents =~ "** Running mix my_hello (inside MyProject)"
|
||||
refute contents =~ "-> Running mix my_hello (inside MyProject)"
|
||||
refute contents =~ "** (Mix.Error) oops"
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -59,7 +59,8 @@ defmodule Mix.TaskTest do
|
||||
Mix.shell(Mix.Shell.IO)
|
||||
Mix.debug(true)
|
||||
|
||||
assert ExUnit.CaptureIO.capture_io(fn -> Mix.Task.run("hello") end) =~ "** Running mix hello"
|
||||
assert ExUnit.CaptureIO.capture_io(fn -> Mix.Task.run("hello") end) =~
|
||||
~r"-> Running mix hello\n<- Ran mix hello in \d+ms"
|
||||
after
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
Mix.debug(false)
|
||||
|
||||
@@ -19,7 +19,11 @@ defmodule Mix.Tasks.ArchiveTest do
|
||||
File.rm_rf!(tmp_path("userhome"))
|
||||
System.put_env("MIX_ARCHIVES", tmp_path("userhome/.mix/archives/"))
|
||||
Mix.Project.push(ArchiveProject)
|
||||
:ok
|
||||
|
||||
on_exit(fn ->
|
||||
Mix.Local.remove_archives()
|
||||
System.delete_env("MIX_ARCHIVES")
|
||||
end)
|
||||
end
|
||||
|
||||
test "archive build" do
|
||||
|
||||
@@ -57,6 +57,19 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
Code.put_compiler_option(:tracers, [])
|
||||
end
|
||||
|
||||
test "compiles a project with a previously set custom tracer" do
|
||||
Process.register(self(), __MODULE__)
|
||||
Code.put_compiler_option(:tracers, [__MODULE__])
|
||||
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Tasks.Compile.Elixir.run([])
|
||||
assert_received {:alias_reference, _meta, A}
|
||||
assert_received {:alias_reference, _meta, B}
|
||||
end)
|
||||
after
|
||||
Code.put_compiler_option(:tracers, [])
|
||||
end
|
||||
|
||||
test "warns when Logger is used but not depended on" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
File.write!("lib/a.ex", """
|
||||
@@ -67,7 +80,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
""")
|
||||
|
||||
message =
|
||||
"Logger.info/1 defined in application :logger is used by the current application but the current application does not directly depend on :logger"
|
||||
"Logger.info/1 defined in application :logger is used by the current application but the current application does not depend on :logger"
|
||||
|
||||
assert capture_io(:stderr, fn ->
|
||||
Mix.Task.run("compile", [])
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Mix.Tasks.Compile.ErlangTest do
|
||||
@tag erlc_options: [{:d, 'foo', 'bar'}]
|
||||
test "raises on invalid erlc_options" do
|
||||
in_fixture("compile_erlang", fn ->
|
||||
assert_raise Mix.Error, ~r"failed with ArgumentError", fn ->
|
||||
assert_raise Mix.Error, ~r"Compiling Erlang file '.*' failed", fn ->
|
||||
capture_io(fn ->
|
||||
Mix.Tasks.Compile.Erlang.run([])
|
||||
end)
|
||||
|
||||
@@ -23,10 +23,12 @@ defmodule Mix.Tasks.Compile.YeccTest do
|
||||
assert %Mix.Task.Compiler.Diagnostic{
|
||||
compiler_name: "yecc",
|
||||
file: ^file,
|
||||
message: "syntax error before: '.'",
|
||||
message: message,
|
||||
position: 1,
|
||||
severity: :error
|
||||
} = diagnostic
|
||||
|
||||
assert message =~ "syntax error before: "
|
||||
end)
|
||||
|
||||
assert File.regular?("src/test_ok.erl")
|
||||
|
||||
@@ -69,11 +69,29 @@ defmodule Mix.Tasks.XrefTest do
|
||||
assert_all_calls(files, output)
|
||||
end
|
||||
|
||||
defp assert_all_calls(files, expected) do
|
||||
test "returns empty on cover compiled modules" do
|
||||
files = %{
|
||||
"lib/a.ex" => """
|
||||
defmodule A do
|
||||
def a, do: A.a()
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
assert_all_calls(files, [], fn ->
|
||||
:cover.start()
|
||||
:cover.compile_beam_directory(to_charlist(Mix.Project.compile_path()))
|
||||
end)
|
||||
after
|
||||
:cover.stop()
|
||||
end
|
||||
|
||||
defp assert_all_calls(files, expected, after_compile \\ fn -> :ok end) do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
generate_files(files)
|
||||
|
||||
Mix.Task.run("compile")
|
||||
after_compile.()
|
||||
assert Enum.sort(Mix.Tasks.Xref.calls()) == Enum.sort(expected)
|
||||
end)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user