├── .credo.exs ├── .dialyzer_ignore.exs ├── .formatter.exs ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── lib ├── elixir_sense.ex └── elixir_sense │ ├── core │ ├── applications.ex │ ├── behaviours.ex │ ├── binding.ex │ ├── bitstring.ex │ ├── builtin_attributes.ex │ ├── builtin_functions.ex │ ├── builtin_types.ex │ ├── compiler.ex │ ├── compiler │ │ ├── bitstring.ex │ │ ├── clauses.ex │ │ ├── dispatch.ex │ │ ├── fn.ex │ │ ├── macro.ex │ │ ├── map.ex │ │ ├── quote.ex │ │ ├── rewrite.ex │ │ ├── state.ex │ │ ├── typespec.ex │ │ └── utils.ex │ ├── erlang_html.ex │ ├── introspection.ex │ ├── metadata.ex │ ├── metadata_builder.ex │ ├── normalized │ │ ├── code.ex │ │ ├── code │ │ │ ├── elixir_sense_formatter.ex │ │ │ ├── elixir_sense_fragment.ex │ │ │ ├── elixir_sense_typespec.ex │ │ │ ├── formatter.ex │ │ │ └── fragment.ex │ │ ├── macro │ │ │ └── env.ex │ │ ├── path.ex │ │ ├── tokenizer.ex │ │ └── typespec.ex │ ├── options.ex │ ├── parser.ex │ ├── reserved_words.ex │ ├── source.ex │ ├── state │ │ ├── attribute_info.ex │ │ ├── call_info.ex │ │ ├── env.ex │ │ ├── mod_fun_info.ex │ │ ├── record_info.ex │ │ ├── spec_info.ex │ │ ├── struct_info.ex │ │ ├── type_info.ex │ │ └── var_info.ex │ ├── struct.ex │ ├── surround_context.ex │ ├── type_ast.ex │ ├── type_inference.ex │ ├── type_inference │ │ └── guard.ex │ └── type_info.ex │ ├── log.ex │ └── providers │ ├── completion │ ├── completion_engine.ex │ ├── generic_reducer.ex │ ├── reducer.ex │ ├── reducers │ │ ├── bitstring.ex │ │ ├── callbacks.ex │ │ ├── complete_engine.ex │ │ ├── docs_snippets.ex │ │ ├── overridable.ex │ │ ├── params.ex │ │ ├── protocol.ex │ │ ├── record.ex │ │ ├── returns.ex │ │ ├── struct.ex │ │ └── type_specs.ex │ └── suggestion.ex │ ├── definition │ └── locator.ex │ ├── hover │ └── docs.ex │ ├── implementation │ └── locator.ex │ ├── location.ex │ ├── location │ └── erl.ex │ ├── plugins │ ├── ecto.ex │ ├── ecto │ │ ├── query.ex │ │ ├── schema.ex │ │ └── types.ex │ ├── module_store.ex │ ├── option.ex │ ├── phoenix.ex │ ├── phoenix │ │ └── scope.ex │ ├── plugin.ex │ └── util.ex │ ├── references │ └── locator.ex │ ├── signature_help │ └── signature.ex │ └── utils │ ├── field.ex │ └── matcher.ex ├── mix.exs ├── mix.lock ├── src ├── .gitignore ├── elixir_sense.erl ├── elixir_sense.hrl ├── elixir_sense_config.erl ├── elixir_sense_interpolation.erl ├── elixir_sense_parser.yrl ├── elixir_sense_tokenizer.erl └── elixir_sense_tokenizer.hrl └── test ├── elixir_sense ├── all_modules_test.exs ├── core │ ├── binding_test.exs │ ├── bitstring_test.exs │ ├── builtin_functions_test.exs │ ├── compiler │ │ └── typespec_test.exs │ ├── compiler_test.exs │ ├── erlang_html_test.exs │ ├── introspection_test.exs │ ├── metadata_builder │ │ ├── alias_test.exs │ │ ├── error_recovery_test.exs │ │ ├── import_test.exs │ │ └── require_test.exs │ ├── metadata_builder_test.exs │ ├── metadata_test.exs │ ├── normalized │ │ ├── code_test.exs │ │ ├── tokenizer_test.exs │ │ └── typespec_test.exs │ ├── options_test.exs │ ├── parser_test.exs │ ├── source_test.exs │ ├── type_inference │ │ └── guard_test.exs │ ├── type_inference_test.exs │ └── type_info_test.exs └── log_test.exs ├── misc └── mock_elixir_src │ └── lib │ └── elixir │ └── lib │ └── string.ex ├── support ├── behaviour_implementations.ex ├── behaviour_with_macrocallbacks.ex ├── callback_opaque.ex ├── case_template_example.ex ├── empty_module.ex ├── example_behaviour.ex ├── example_protocol.ex ├── fixtures │ └── metadata_builder │ │ ├── alias │ │ ├── after_strct_with_implementation.ex │ │ ├── alias_12.ex │ │ ├── alias_of_alias.ex │ │ ├── empty.ex │ │ ├── external.ex │ │ ├── in_submodule.ex │ │ ├── inherit_function.ex │ │ ├── inherit_submodule.ex │ │ ├── module_special.ex │ │ ├── module_special_12.ex │ │ ├── module_special_submodule.ex │ │ ├── module_special_with_as.ex │ │ ├── no_leak_block.ex │ │ ├── no_leak_clause.ex │ │ ├── no_leak_function.ex │ │ ├── no_leak_submodule.ex │ │ ├── no_unalias_nested.ex │ │ ├── noop.ex │ │ ├── one_part.ex │ │ ├── one_part_with_as.ex │ │ ├── realias.ex │ │ ├── realias_in_scope.ex │ │ ├── require_with_as.ex │ │ ├── require_with_warn_as.ex │ │ ├── simple_alias.ex │ │ ├── submodule.ex │ │ ├── submodule_external.ex │ │ ├── submodule_external_inherit.ex │ │ ├── submodule_external_inside.ex │ │ ├── submodule_external_special.ex │ │ ├── submodule_external_special_inside.ex │ │ ├── submodule_external_with_alias.ex │ │ ├── submodule_external_with_alias_special.ex │ │ ├── submodule_nested.ex │ │ ├── submodule_nested_external.ex │ │ ├── unalias.ex │ │ ├── with_as.ex │ │ ├── with_as_erlang.ex │ │ ├── with_warn.ex │ │ └── with_warn_as.ex │ │ ├── import │ │ ├── elixir_behaviour.ex │ │ ├── empty.ex │ │ ├── erlang.ex │ │ ├── erlang_behaviour.ex │ │ ├── except_list.ex │ │ ├── except_modify.ex │ │ ├── external.ex │ │ ├── import_12.ex │ │ ├── import_of_alias.ex │ │ ├── in_submodule.ex │ │ ├── inherit_function.ex │ │ ├── inherit_submodule.ex │ │ ├── no_leak_block.ex │ │ ├── no_leak_clause.ex │ │ ├── no_leak_function.ex │ │ ├── no_leak_submodule.ex │ │ ├── one_part.ex │ │ ├── only_functions.ex │ │ ├── only_list.ex │ │ ├── only_macros.ex │ │ ├── only_sigils.ex │ │ ├── only_underscored.ex │ │ ├── overwrite.ex │ │ ├── overwrite_in_scope.ex │ │ ├── overwrite_no_leak.ex │ │ ├── simple_import.ex │ │ ├── transitive.ex │ │ └── with_warn.ex │ │ ├── modules.ex │ │ └── require │ │ ├── empty.ex │ │ ├── external.ex │ │ ├── import.ex │ │ ├── in_submodule.ex │ │ ├── inherit_function.ex │ │ ├── inherit_submodule.ex │ │ ├── no_leak_block.ex │ │ ├── no_leak_clause.ex │ │ ├── no_leak_function.ex │ │ ├── no_leak_submodule.ex │ │ ├── one_part.ex │ │ ├── one_part_with_as.ex │ │ ├── require_12.ex │ │ ├── require_of_alias.ex │ │ ├── simple_require.ex │ │ ├── submodule.ex │ │ ├── use.ex │ │ ├── with_as.ex │ │ └── with_warn.ex ├── functions_with_default_args.ex ├── functions_with_return_spec.ex ├── functions_with_the_same_name.ex ├── macro_generated.ex ├── macro_hygiene.ex ├── module_with_builtin_type_shadowing.ex ├── module_with_functions.ex ├── module_with_many_clauses.ex ├── module_with_private_types.ex ├── module_with_record.ex ├── module_with_struct.ex ├── module_with_types.ex ├── module_with_typespecs.ex ├── modules_with_docs.ex ├── modules_with_references.ex ├── options.ex ├── overridable_function.ex ├── references_tracer.ex ├── same_module.ex ├── stuct_with_typespec.ex ├── subscriber.ex ├── subscription.ex ├── types_with_multiple_arity.ex └── use_example.ex └── test_helper.exs /.dialyzer_ignore.exs: -------------------------------------------------------------------------------- 1 | [{"lib/elixir_sense/core/normalized/tokenizer.ex", :pattern_match}] 2 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: 3 | Enum.flat_map( 4 | ["{mix,.formatter,.credo,run,run_test}.exs", "{config,lib,test}/**/*.{ex,exs}"], 5 | &Path.wildcard(&1, match_dot: true) 6 | ) -- ["test/support/modules_with_references.ex"] 7 | ] 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | 19 | .elixir_ls 20 | .vscode 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Contact: elixir-ls-coc@googlegroups.com 4 | 5 | ## Why have a Code of Conduct? 6 | 7 | As contributors and maintainers of this project, we are committed to providing a friendly, safe and welcoming environment for all, regardless of age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic. 8 | 9 | The goal of the Code of Conduct is to specify a baseline standard of behavior so that people with different social values and communication styles can talk about Elixir effectively, productively, and respectfully, even in face of disagreements. The Code of Conduct also provides a mechanism for resolving conflicts in the community when they arise. 10 | 11 | ## Our Values 12 | 13 | These are the values ElixirLS developers should aspire to: 14 | 15 | * Be friendly and welcoming 16 | * Be kind 17 | * Remember that people have varying communication styles and that not everyone is using their native language. (Meaning and tone can be lost in translation.) 18 | * Interpret the arguments of others in good faith, do not seek to disagree. 19 | * When we do disagree, try to understand why. 20 | * Be thoughtful 21 | * Productive communication requires effort. Think about how your words will be interpreted. 22 | * Remember that sometimes it is best to refrain entirely from commenting. 23 | * Be respectful 24 | * In particular, respect differences of opinion. It is important that we resolve disagreements and differing views constructively. 25 | * Be constructive 26 | * Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation. 27 | * Avoid unconstructive criticism: don't merely decry the current state of affairs; offer — or at least solicit — suggestions as to how things may be improved. 28 | * Avoid harsh words and stern tone: we are all aligned towards the well-being of the community and the progress of the ecosystem. Harsh words exclude, demotivate, and lead to unnecessary conflict. 29 | * Avoid snarking (pithy, unproductive, sniping comments). 30 | * Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults towards a project, person or group). 31 | * Be responsible 32 | * What you say and do matters. Take responsibility for your words and actions, including their consequences, whether intended or otherwise. 33 | 34 | The following actions are explicitly forbidden: 35 | 36 | * Insulting, demeaning, hateful, or threatening remarks. 37 | * Discrimination based on age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic. 38 | * Bullying or systematic harassment. 39 | * Unwelcome sexual advances. 40 | * Incitement to any of these. 41 | 42 | ## Where does the Code of Conduct apply? 43 | 44 | If you participate in or contribute to the ElixirLS in any way, you are encouraged to follow the Code of Conduct while doing so. 45 | 46 | Explicit enforcement of the Code of Conduct applies to the official mediums operated by the ElixirLS project: 47 | 48 | * The [official GitHub projects][1] and code reviews. 49 | 50 | Other ElixirLS activities (such as conferences, meetups, and unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information. 51 | 52 | Project maintainers may block, remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. 53 | 54 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by emailing: elixir-ls-coc@googlegroups.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. **All reports will be kept confidential**. 55 | 56 | **The goal of the Code of Conduct is to resolve conflicts in the most harmonious way possible**. We hope that in most cases issues may be resolved through polite discussion and mutual agreement. Bannings and other forceful measures are to be employed only as a last resort. **Do not** post about the issue publicly or try to rally sentiment against a particular individual or group. 57 | 58 | ## Acknowledgements 59 | 60 | This document was based on the Code of Conduct from the Go project (dated Sep/2021) and the Contributor Covenant (v1.4). 61 | 62 | [1]: https://github.com/elixir-lsp/ 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marlus Saraiva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ElixirSense [![Actions Status](https://img.shields.io/github/actions/workflow/status/elixir-lsp/elixir_sense/ci.yml?branch=master)](github/actions/workflow/status/elixir-lsp/elixir_sense/ci.yml?branch=master) 2 | 3 | An API for Elixir projects that provides building blocks for code completion, documentation, go/jump to definition, signature info and more via AST inspection and runtime introspection. 4 | 5 | ## Usage 6 | 7 | ``` 8 | defp deps do 9 | [ 10 | {:elixir_sense, github: "elixir-lsp/elixir_sense"}, 11 | ] 12 | end 13 | ``` 14 | 15 | ## Testing 16 | 17 | ``` 18 | $ mix deps.get 19 | $ mix test 20 | ``` 21 | 22 | A few of the tests require a source installation of Elixir which you can accomplish with [asdf](https://github.com/asdf-vm/asdf-elixir) (use `ref:v1.12.3`) or [kiex](https://github.com/taylor/kiex) 23 | 24 | To run the tests that require a source installation of Elixir run: 25 | ``` 26 | mix test --include requires_source 27 | ``` 28 | 29 | For coverage: 30 | 31 | ``` 32 | mix coveralls 33 | ``` 34 | 35 | ## Credits 36 | 37 | - This project probably wouldn't even exist without all the work done by Samuel Tonini and all contributors from [alchemist-server](https://github.com/tonini/alchemist-server). 38 | - The Expand feature was inspired by the [mex](https://github.com/mrluc/mex) tool by Luc Fueston. There's also a very nice post where he describes the whole process of [Building A Macro-Expansion Helper for IEx](http://blog.maketogether.com/building-a-macro-expansion-helper/). 39 | - This project includes modified source code from Elixir project [IEx autocomplete](https://github.com/elixir-lang/elixir/tree/v1.9/lib/iex) Copyright (c) 2012 Plataformatec, which powers introspection and suggestions features 40 | - This project includes modified source code from ExDoc project [ExDoc](https://github.com/elixir-lang/ex_doc) Copyright (c) 2012 Plataformatec, which powers documentation retrieval and formatting 41 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :elixir_sense, 4 | logging_enabled: true 5 | 6 | import_config "#{Mix.env()}.exs" 7 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | config :elixir_sense, logging_enabled: false 3 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | -------------------------------------------------------------------------------- /lib/elixir_sense.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense do 2 | @moduledoc """ 3 | ElxirSense is a Elixir library that implements useful features for any editor/tool that needs 4 | to introspect context-aware information about Elixir source code. 5 | 6 | This module provides the basic functionality for context-aware code completion, docs, signature info and more. 7 | """ 8 | 9 | alias ElixirSense.Core.Applications 10 | alias ElixirSense.Core.Parser 11 | 12 | @type callee_t :: {module, atom, non_neg_integer} 13 | 14 | @type call_t :: %{ 15 | callee: callee_t, 16 | file: nil | String.t(), 17 | line: nil | pos_integer, 18 | column: nil | pos_integer 19 | } 20 | @type call_trace_t :: %{optional(callee_t) => [call_t]} 21 | 22 | @doc ~S""" 23 | Returns a sorted list of all available modules 24 | 25 | ## Example 26 | 27 | iex> ":application" in ElixirSense.all_modules() 28 | true 29 | 30 | iex> "Version.Parser" in ElixirSense.all_modules() 31 | true 32 | 33 | """ 34 | @spec all_modules() :: list(String.t()) 35 | def all_modules do 36 | Applications.get_modules_from_applications() 37 | |> Enum.map(&inspect/1) 38 | |> Enum.sort() 39 | end 40 | 41 | @doc ~S""" 42 | Provides an error tolerant parser 43 | 44 | ## Example 45 | 46 | iex> code = ~S''' 47 | ...> defmodule do 48 | ...> end 49 | ...> ''' 50 | iex> ElixirSense.string_to_quoted(code, 1) 51 | {:ok, {:defmodule, [do: [line: 1, column: 11], end: [line: 2, column: 1], line: 1, column: 1], [[do: {:__block__, [], []}]]}} 52 | """ 53 | @spec string_to_quoted( 54 | String.t(), 55 | {pos_integer, pos_integer} | nil, 56 | non_neg_integer, 57 | boolean, 58 | keyword 59 | ) :: 60 | {:ok, Macro.t()} | {:error, any()} 61 | def string_to_quoted( 62 | source, 63 | cursor_position \\ nil, 64 | error_threshold \\ 6, 65 | fallback_to_container_cursor_to_quoted \\ true, 66 | parser_options \\ [] 67 | ) do 68 | string_to_ast_options = [ 69 | errors_threshold: error_threshold, 70 | cursor_position: cursor_position, 71 | fallback_to_container_cursor_to_quoted: fallback_to_container_cursor_to_quoted, 72 | parser_options: parser_options 73 | ] 74 | 75 | case Parser.string_to_ast(source, string_to_ast_options) do 76 | {:ok, ast, _source, _error} -> {:ok, ast} 77 | other = {:error, _} -> other 78 | end 79 | end 80 | 81 | defdelegate docs(code, line, column, options \\ []), to: ElixirSense.Providers.Hover.Docs 82 | 83 | defdelegate definition(code, line, column, options \\ []), 84 | to: ElixirSense.Providers.Definition.Locator 85 | 86 | defdelegate implementations(code, line, column, options \\ []), 87 | to: ElixirSense.Providers.Implementation.Locator 88 | 89 | defdelegate suggestions(code, line, column, options \\ []), 90 | to: ElixirSense.Providers.Completion.Suggestion 91 | 92 | defdelegate signature(code, line, column, options \\ []), 93 | to: ElixirSense.Providers.SignatureHelp.Signature 94 | 95 | defdelegate references(code, line, column, trace, options \\ []), 96 | to: ElixirSense.Providers.References.Locator 97 | end 98 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/applications.ex: -------------------------------------------------------------------------------- 1 | # This file includes modified code extracted from the elixir project. Namely: 2 | # 3 | # https://github.com/elixir-lang/elixir/blob/v1.10/lib/iex/lib/iex/autocomplete.ex 4 | # 5 | # The original code is licensed as follows: 6 | # 7 | # Copyright 2012 Plataformatec 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # https://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | 21 | defmodule ElixirSense.Core.Applications do 22 | @moduledoc """ 23 | This module contains helper functions for introspecting running OTP applications 24 | """ 25 | 26 | @spec get_modules_from_applications() :: [module] 27 | # TODO use :code.all_available |> Enum.map(fn {m, _, _} -> :"#{m}" end) on otp 23+ 28 | # as it returns more 29 | def get_modules_from_applications do 30 | # :erts app is not loaded by default 31 | _ = Application.load(:erts) 32 | 33 | for [app] <- loaded_applications(), 34 | module <- safe_get_modules(app) do 35 | module 36 | end 37 | end 38 | 39 | defp safe_get_modules(app) do 40 | case :application.get_key(app, :modules) do 41 | {:ok, modules} -> modules 42 | :undefined -> [] 43 | end 44 | end 45 | 46 | defp loaded_applications do 47 | # If we invoke :application.loaded_applications/0, 48 | # it can error if we don't call safe_fixtable before. 49 | # Since in both cases we are reaching over the 50 | # application controller internals, we choose to match 51 | # for performance. 52 | :ets.match(:ac_tab, {{:loaded, :"$1"}, :_}) 53 | end 54 | 55 | def get_application(module) do 56 | Enum.find_value(:application.loaded_applications(), fn {app, _, _} -> 57 | case :application.get_key(app, :modules) do 58 | {:ok, modules} -> 59 | if module in modules do 60 | app 61 | end 62 | 63 | :undefined -> 64 | nil 65 | end 66 | end) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/behaviours.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Behaviours do 2 | @moduledoc false 3 | alias ElixirSense.Core.Applications 4 | 5 | @spec get_module_behaviours(module) :: [module] 6 | def get_module_behaviours(module) do 7 | if Code.ensure_loaded?(module) do 8 | module.module_info(:attributes) |> Keyword.get_values(:behaviour) |> Enum.concat() 9 | else 10 | [] 11 | end 12 | end 13 | 14 | @spec get_all_behaviour_implementations(module) :: [module] 15 | def get_all_behaviour_implementations(behaviour) do 16 | # this function can take a few seconds 17 | # unfortunately it does not benefit from conversion to Task.async_stream 18 | # at least on otp 23 19 | 20 | # TODO consider changing this to :code.all_available when otp 23 is required 21 | all_loaded = 22 | :code.all_loaded() 23 | |> Enum.map(&(&1 |> elem(0))) 24 | 25 | from_apps = 26 | case :code.get_mode() do 27 | :interactive -> 28 | Applications.get_modules_from_applications() 29 | 30 | _ -> 31 | [] 32 | end 33 | 34 | (all_loaded ++ from_apps) 35 | |> Enum.uniq() 36 | |> Enum.filter(fn mod -> 37 | Code.ensure_loaded?(mod) and 38 | behaviour in Enum.flat_map(mod.module_info(:attributes), fn 39 | {:behaviour, behaviours} when is_list(behaviours) -> 40 | behaviours 41 | 42 | _ -> 43 | [] 44 | end) 45 | end) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/bitstring.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Bitstring do 2 | @moduledoc false 3 | 4 | @types [ 5 | :integer, 6 | :float, 7 | :bitstring, 8 | :binary, 9 | :utf8, 10 | :utf16, 11 | :utf32 12 | ] 13 | 14 | @utf_types [ 15 | :utf8, 16 | :utf16, 17 | :utf32 18 | ] 19 | 20 | @type_aliases %{ 21 | bits: :bitstring, 22 | bytes: :binary 23 | } 24 | 25 | @modifiers %{ 26 | signed: [:integer], 27 | unsigned: [:integer], 28 | little: [:integer, :float, :utf16, :utf32], 29 | big: [:integer, :float, :utf16, :utf32], 30 | native: [:integer, :float, :utf16, :utf32] 31 | } 32 | 33 | @sign_modifiers [:signed, :unsigned] 34 | @endianness_modifiers [:little, :big, :native] 35 | @default %{ 36 | type: nil, 37 | sign_modifier: nil, 38 | endianness_modifier: nil, 39 | size: nil, 40 | unit: nil 41 | } 42 | 43 | def parse(binary, acc \\ @default) 44 | 45 | for type <- @types do 46 | def parse(<>, acc) do 47 | parse(rest, %{acc | type: unquote(type)}) 48 | end 49 | end 50 | 51 | for {type_alias, type} <- @type_aliases do 52 | def parse(<>, acc) do 53 | parse(rest, %{acc | type: unquote(type)}) 54 | end 55 | end 56 | 57 | for sign_modifier <- @sign_modifiers do 58 | def parse(<>, acc) do 59 | parse(rest, %{acc | sign_modifier: unquote(sign_modifier)}) 60 | end 61 | end 62 | 63 | for endianness_modifier <- @endianness_modifiers do 64 | def parse(<>, acc) do 65 | parse(rest, %{acc | endianness_modifier: unquote(endianness_modifier)}) 66 | end 67 | end 68 | 69 | def parse(<<"-", rest::binary>>, acc), do: parse(rest, acc) 70 | 71 | def parse(<<"size", rest::binary>>, acc) do 72 | parse(rest, %{acc | size: true}) 73 | end 74 | 75 | def parse(<<"unit", rest::binary>>, acc) do 76 | parse(rest, %{acc | unit: true}) 77 | end 78 | 79 | def parse(<<_::binary-size(1), rest::binary>>, acc), do: parse(rest, acc) 80 | 81 | def parse(<<>>, acc), do: acc 82 | 83 | def available_options(map) do 84 | available_types(map) 85 | |> Kernel.++(available_sign_modifiers(map)) 86 | |> Kernel.++(available_endianness_modifiers(map)) 87 | |> Kernel.++(available_size(map)) 88 | |> Kernel.++(available_unit(map)) 89 | end 90 | 91 | def available_types(%{type: nil, sign_modifier: nil, endianness_modifier: nil} = map), 92 | do: filter_utf(map, @types) 93 | 94 | def available_types( 95 | %{type: nil, sign_modifier: nil, endianness_modifier: endianness_modifier} = map 96 | ), 97 | do: filter_utf(map, @modifiers[endianness_modifier]) 98 | 99 | def available_types(%{type: nil}), do: [:integer] 100 | def available_types(_), do: [] 101 | 102 | def available_sign_modifiers(%{type: type, sign_modifier: nil}) when type in [nil, :integer], 103 | do: @sign_modifiers 104 | 105 | def available_sign_modifiers(_), do: [] 106 | 107 | def available_endianness_modifiers(%{type: type, endianness_modifier: nil}) 108 | when type in [nil, :integer, :utf16, :utf32], 109 | do: @endianness_modifiers 110 | 111 | def available_endianness_modifiers(%{type: :float, endianness_modifier: nil}), 112 | do: [:little, :big] 113 | 114 | def available_endianness_modifiers(_), do: [] 115 | 116 | # It's not documented but as of elixir 1.13 size and unit are not supported on utf types 117 | # and will fail to compile 118 | 119 | def available_size(%{size: nil, type: type}) when type not in @utf_types, do: [:size] 120 | def available_size(_), do: [] 121 | 122 | def available_unit(%{unit: nil, type: type}) when type not in @utf_types, do: [:unit] 123 | def available_unit(_), do: [] 124 | 125 | defp filter_utf(%{size: nil, unit: nil}, list), do: list 126 | defp filter_utf(_, list), do: list -- @utf_types 127 | end 128 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/builtin_attributes.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.BuiltinAttributes do 2 | @moduledoc false 3 | @list ~w( 4 | optional_callbacks 5 | behaviour 6 | impl 7 | derive 8 | enforce_keys 9 | compile 10 | deprecated 11 | dialyzer 12 | file 13 | external_resource 14 | on_load 15 | on_definition 16 | vsn 17 | after_compile 18 | after_verify 19 | before_compile 20 | fallback_to_any 21 | undefined_impl_description 22 | type 23 | typep 24 | opaque 25 | spec 26 | callback 27 | macrocallback 28 | typedoc 29 | doc 30 | moduledoc 31 | for 32 | protocol 33 | nifs 34 | )a 35 | 36 | def all, do: @list 37 | 38 | def docs(attribute) when attribute in @list do 39 | case Module.reserved_attributes() do 40 | %{^attribute => %{doc: doc}} -> 41 | doc 42 | 43 | _ -> 44 | # Older Elixir versions haven't document these attributes 45 | # Reference: https://github.com/elixir-lang/elixir/commit/14c55d15afbf08b0d8289a4399a15b4109b6ac5a 46 | case attribute do 47 | :enforce_keys -> 48 | "Ensures the given keys are always set when building the struct defined in the current module." 49 | 50 | :fallback_to_any -> 51 | "If set to `true` generates a default protocol implementation " <> 52 | "for all types (inside `defprotocol`)." 53 | 54 | :for -> 55 | "The current module/type a protocol implementation is being defined for (inside `defimpl`)." 56 | 57 | :protocol -> 58 | "The current protocol being implemented (inside `defimpl`)." 59 | 60 | _ -> 61 | nil 62 | end 63 | end 64 | end 65 | 66 | def docs(_), do: nil 67 | end 68 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/compiler/rewrite.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Compiler.Rewrite do 2 | def inline(module, fun, arity) do 3 | :elixir_rewrite.inline(module, fun, arity) 4 | end 5 | 6 | def rewrite(context, receiver, dot_meta, right, meta, e_args, s) do 7 | do_rewrite(context, receiver, dot_meta, right, meta, e_args, s) 8 | end 9 | 10 | defp do_rewrite(_, :erlang, _, :+, _, [arg], _s) when is_number(arg), do: {:ok, arg} 11 | 12 | defp do_rewrite(_, :erlang, _, :-, _, [arg], _s) when is_number(arg), do: {:ok, -arg} 13 | 14 | defp do_rewrite(:match, receiver, dot_meta, right, meta, e_args, s) do 15 | if function_exported?(:elixir_rewrite, :match_rewrite, 5) do 16 | :elixir_rewrite.match_rewrite(receiver, dot_meta, right, meta, e_args) 17 | else 18 | :elixir_rewrite.match(receiver, dot_meta, right, meta, e_args, s) 19 | end 20 | end 21 | 22 | if Version.match?(System.version(), "< 1.14.0-dev") do 23 | defp do_rewrite(:guard, receiver, dot_meta, right, meta, e_args, s) do 24 | :elixir_rewrite.guard_rewrite(receiver, dot_meta, right, meta, e_args) 25 | end 26 | else 27 | defp do_rewrite(:guard, receiver, dot_meta, right, meta, e_args, s) do 28 | if function_exported?(:elixir_rewrite, :match_rewrite, 5) do 29 | # elixir uses guard context for error messages 30 | :elixir_rewrite.guard_rewrite(receiver, dot_meta, right, meta, e_args, "guard") 31 | else 32 | :elixir_rewrite.guard(receiver, dot_meta, right, meta, e_args, s) 33 | end 34 | end 35 | end 36 | 37 | defp do_rewrite(_, receiver, dot_meta, right, meta, e_args, _s) do 38 | {:ok, :elixir_rewrite.rewrite(receiver, dot_meta, right, meta, e_args)} 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/compiler/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Compiler.Utils do 2 | def generated([{:generated, true} | _] = meta), do: meta 3 | def generated(meta), do: [{:generated, true} | meta] 4 | 5 | def split_last([]), do: {[], []} 6 | 7 | def split_last(list), do: split_last(list, []) 8 | 9 | defp split_last([h], acc), do: {Enum.reverse(acc), h} 10 | 11 | defp split_last([h | t], acc), do: split_last(t, [h | acc]) 12 | 13 | def split_opts(args) do 14 | case split_last(args) do 15 | {outer_cases, outer_opts} when is_list(outer_opts) -> 16 | case split_last(outer_cases) do 17 | {inner_cases, inner_opts} when is_list(inner_opts) -> 18 | {inner_cases, inner_opts ++ outer_opts} 19 | 20 | _ -> 21 | {outer_cases, outer_opts} 22 | end 23 | 24 | _ -> 25 | {args, []} 26 | end 27 | end 28 | 29 | def get_line(opts) when is_list(opts) do 30 | case Keyword.fetch(opts, :line) do 31 | {:ok, line} when is_integer(line) -> line 32 | _ -> 0 33 | end 34 | end 35 | 36 | def extract_guards({:when, _, [left, right]}), do: {left, extract_or_guards(right)} 37 | def extract_guards(term), do: {term, []} 38 | 39 | def extract_or_guards({:when, _, [left, right]}), do: [left | extract_or_guards(right)] 40 | def extract_or_guards(term), do: [term] 41 | 42 | def select_with_cursor(ast_list) do 43 | Enum.find(ast_list, &has_cursor?/1) 44 | end 45 | 46 | def has_cursor?(ast) do 47 | # TODO rewrite to lazy prewalker 48 | {_, result} = 49 | Macro.prewalk(ast, false, fn 50 | _node, true -> 51 | {nil, true} 52 | 53 | {:__cursor__, _, list}, _state when is_list(list) -> 54 | {nil, true} 55 | 56 | node, false -> 57 | {node, false} 58 | end) 59 | 60 | result 61 | end 62 | 63 | def defdelegate_each(fun, opts) when is_list(opts) do 64 | # TODO Remove on elixir v2.0 65 | append_first? = Keyword.get(opts, :append_first, false) 66 | 67 | {name, args} = 68 | case fun do 69 | {:when, _, [_left, right]} -> 70 | raise ArgumentError, 71 | "guards are not allowed in defdelegate/2, got: when #{Macro.to_string(right)}" 72 | 73 | _ -> 74 | case Macro.decompose_call(fun) do 75 | {_, _} = pair -> pair 76 | _ -> raise ArgumentError, "invalid syntax in defdelegate #{Macro.to_string(fun)}" 77 | end 78 | end 79 | 80 | as = Keyword.get(opts, :as, name) 81 | as_args = build_as_args(args, append_first?) 82 | 83 | {name, args, as, as_args} 84 | end 85 | 86 | defp build_as_args(args, append_first?) do 87 | as_args = :lists.map(&build_as_arg/1, args) 88 | 89 | case append_first? do 90 | true -> tl(as_args) ++ [hd(as_args)] 91 | false -> as_args 92 | end 93 | end 94 | 95 | # elixir validates arg 96 | defp build_as_arg({:\\, _, [arg, _default_arg]}), do: arg 97 | defp build_as_arg(arg), do: arg 98 | end 99 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/metadata_builder.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.MetadataBuilder do 2 | @moduledoc """ 3 | This module is responsible for building/retrieving environment information from an AST. 4 | """ 5 | 6 | alias ElixirSense.Core.Compiler.State 7 | alias ElixirSense.Core.Compiler 8 | 9 | @doc """ 10 | Traverses the AST building/retrieving the environment information. 11 | It returns a `ElixirSense.Core.State` struct containing the information. 12 | """ 13 | @spec build(Macro.t(), nil | {pos_integer, pos_integer}) :: State.t() 14 | def build(ast, cursor_position \\ nil) do 15 | state_initial = initial_state(cursor_position) 16 | 17 | {_ast, state, env} = Compiler.expand(ast, state_initial, Compiler.env()) 18 | 19 | state 20 | |> Compiler.collect_traces() 21 | |> State.remove_attributes_scope() 22 | |> State.remove_vars_scope(state_initial) 23 | |> State.remove_module(env) 24 | end 25 | 26 | def initial_state(cursor_position) do 27 | %State{ 28 | cursor_position: cursor_position, 29 | prematch: 30 | if Version.match?(System.version(), ">= 1.15.0-dev") do 31 | if(Version.match?(System.version(), ">= 1.18.0-dev"), 32 | do: :none, 33 | else: Code.get_compiler_option(:on_undefined_variable) 34 | ) 35 | else 36 | :warn 37 | end 38 | } 39 | end 40 | 41 | def default_env(cursor_position \\ nil) do 42 | macro_env = Compiler.env() 43 | state_initial = initial_state(cursor_position) 44 | State.get_current_env(state_initial, macro_env) 45 | end 46 | 47 | # defp post_string_literal(ast, _state, _line, str) do 48 | # str 49 | # |> Source.split_lines() 50 | # |> Enum.with_index() 51 | # # |> Enum.reduce(state, fn {_s, i}, acc -> add_current_env_to_line(acc, line + i) end) 52 | # # |> result(ast) 53 | # end 54 | 55 | # # Any other tuple with a line 56 | # defp pre({_, meta, _} = ast, state) do 57 | # case Keyword.get(meta, :line) do 58 | # nil -> 59 | # {ast, state} 60 | 61 | # _line -> 62 | # state 63 | # # |> add_current_env_to_line(line) 64 | # # |> result(ast) 65 | # end 66 | # end 67 | 68 | # # String literal 69 | # defp post({_, [no_call: true, line: line, column: _column], [str]} = ast, state) 70 | # when is_binary(str) do 71 | # post_string_literal(ast, state, line, str) 72 | # end 73 | 74 | # # String literal in sigils 75 | # defp post({:<<>>, [indentation: _, line: line, column: _column], [str]} = ast, state) 76 | # when is_binary(str) do 77 | # post_string_literal(ast, state, line, str) 78 | # end 79 | end 80 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/normalized/code/elixir_sense_formatter.ex: -------------------------------------------------------------------------------- 1 | # This file includes modified code extracted from the elixir project. Namely: 2 | # 3 | # https://raw.githubusercontent.com/elixir-lang/elixir/v1.13.4/lib/elixir/lib/code/formatter.ex 4 | # 5 | # The original code is licensed as follows: 6 | # 7 | # Copyright 2012 Plataformatec 8 | # Copyright 2021 The Elixir Team 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | 22 | # The only changes are module renames 23 | 24 | defmodule ElixirSense.Core.Normalized.Code.ElixirSense.Formatter do 25 | @locals_without_parens [ 26 | # Special forms 27 | alias: 1, 28 | alias: 2, 29 | case: 2, 30 | cond: 1, 31 | for: :*, 32 | import: 1, 33 | import: 2, 34 | quote: 1, 35 | quote: 2, 36 | receive: 1, 37 | require: 1, 38 | require: 2, 39 | try: 1, 40 | with: :*, 41 | 42 | # Kernel 43 | def: 1, 44 | def: 2, 45 | defp: 1, 46 | defp: 2, 47 | defguard: 1, 48 | defguardp: 1, 49 | defmacro: 1, 50 | defmacro: 2, 51 | defmacrop: 1, 52 | defmacrop: 2, 53 | defmodule: 2, 54 | defdelegate: 2, 55 | defexception: 1, 56 | defoverridable: 1, 57 | defstruct: 1, 58 | destructure: 2, 59 | raise: 1, 60 | raise: 2, 61 | reraise: 2, 62 | reraise: 3, 63 | if: 2, 64 | unless: 2, 65 | use: 1, 66 | use: 2, 67 | 68 | # Stdlib, 69 | defrecord: 2, 70 | defrecord: 3, 71 | defrecordp: 2, 72 | defrecordp: 3, 73 | 74 | # Testing 75 | assert: 1, 76 | assert: 2, 77 | assert_in_delta: 3, 78 | assert_in_delta: 4, 79 | assert_raise: 2, 80 | assert_raise: 3, 81 | assert_receive: 1, 82 | assert_receive: 2, 83 | assert_receive: 3, 84 | assert_received: 1, 85 | assert_received: 2, 86 | doctest: 1, 87 | doctest: 2, 88 | refute: 1, 89 | refute: 2, 90 | refute_in_delta: 3, 91 | refute_in_delta: 4, 92 | refute_receive: 1, 93 | refute_receive: 2, 94 | refute_receive: 3, 95 | refute_received: 1, 96 | refute_received: 2, 97 | setup: 1, 98 | setup: 2, 99 | setup_all: 1, 100 | setup_all: 2, 101 | test: 1, 102 | test: 2, 103 | 104 | # Mix config 105 | config: 2, 106 | config: 3, 107 | import_config: 1 108 | ] 109 | 110 | @doc """ 111 | Lists all default locals without parens. 112 | """ 113 | def locals_without_parens do 114 | @locals_without_parens 115 | end 116 | 117 | @doc """ 118 | Checks if a function is a local without parens. 119 | """ 120 | def local_without_parens?(fun, arity, locals_without_parens) do 121 | arity > 0 and 122 | Enum.any?(locals_without_parens, fn {key, val} -> 123 | key == fun and (val == :* or val == arity) 124 | end) 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/normalized/code/formatter.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Normalized.Code.Formatter do 2 | def locals_without_parens do 3 | cond do 4 | Version.match?(System.version(), ">= 1.14.0-dev") -> 5 | apply(Code.Formatter, :locals_without_parens, []) 6 | 7 | true -> 8 | # on 1.13 use our version as it has all the fixes from last 1.13 release 9 | apply(ElixirSense.Core.Normalized.Code.ElixirSense.Formatter, :locals_without_parens, []) 10 | end 11 | end 12 | 13 | def local_without_parens?(fun, arity, locals_without_parens) do 14 | cond do 15 | Version.match?(System.version(), ">= 1.14.0-dev") -> 16 | apply(Code.Formatter, :local_without_parens?, [fun, arity, locals_without_parens]) 17 | 18 | true -> 19 | # on 1.13 use our version as it has all the fixes from last 1.13 release 20 | apply(ElixirSense.Core.Normalized.Code.ElixirSense.Formatter, :local_without_parens?, [ 21 | fun, 22 | arity, 23 | locals_without_parens 24 | ]) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/normalized/code/fragment.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Normalized.Code.Fragment do 2 | @moduledoc false 3 | require Logger 4 | 5 | def cursor_context(string, opts \\ []) do 6 | cond do 7 | Version.match?(System.version(), ">= 1.14.0-dev") -> 8 | apply(Code.Fragment, :cursor_context, [string, opts]) 9 | 10 | true -> 11 | # on 1.13 use our version as it has all the fixes from last 1.13 release 12 | apply(ElixirSense.Core.Normalized.Code.ElixirSense.Fragment, :cursor_context, [ 13 | string, 14 | opts 15 | ]) 16 | end 17 | rescue 18 | e -> 19 | if Version.match?(System.version(), ">= 1.18.0-dev") do 20 | Logger.error( 21 | "Code.Fragment.cursor_context raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 22 | ) 23 | 24 | reraise e, __STACKTRACE__ 25 | else 26 | :none 27 | end 28 | end 29 | 30 | def surround_context(fragment, position, options \\ []) do 31 | cond do 32 | Version.match?(System.version(), ">= 1.14.0-dev") -> 33 | apply(Code.Fragment, :surround_context, [fragment, position, options]) 34 | 35 | true -> 36 | # on 1.13 use our version as it has all the fixes from last 1.13 release 37 | apply(ElixirSense.Core.Normalized.Code.ElixirSense.Fragment, :surround_context, [ 38 | fragment, 39 | position, 40 | options 41 | ]) 42 | end 43 | rescue 44 | e -> 45 | if Version.match?(System.version(), ">= 1.18.0-dev") do 46 | Logger.error( 47 | "Code.Fragment.surround_context raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 48 | ) 49 | 50 | reraise e, __STACKTRACE__ 51 | else 52 | :none 53 | end 54 | end 55 | 56 | def container_cursor_to_quoted(fragment, opts \\ []) do 57 | cond do 58 | Version.match?(System.version(), ">= 1.14.0-dev") -> 59 | apply(Code.Fragment, :container_cursor_to_quoted, [fragment, opts]) 60 | 61 | true -> 62 | # on 1.13 use our version as it has all the fixes from last 1.13 release 63 | apply( 64 | ElixirSense.Core.Normalized.Code.ElixirSense.Fragment, 65 | :container_cursor_to_quoted, 66 | [fragment, opts] 67 | ) 68 | end 69 | rescue 70 | e -> 71 | if Version.match?(System.version(), ">= 1.18.0-dev") do 72 | try do 73 | Logger.error( 74 | "Code.Fragment.container_cursor_to_quoted raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 75 | ) 76 | rescue 77 | _ -> :ok 78 | end 79 | 80 | reraise e, __STACKTRACE__ 81 | else 82 | {:error, {[line: 1, column: 1], "", ""}} 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/normalized/tokenizer.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Normalized.Tokenizer do 2 | @moduledoc """ 3 | Handles tokenization of Elixir code snippets 4 | 5 | Uses private api :elixir_tokenizer 6 | """ 7 | require Logger 8 | 9 | @spec tokenize(String.t(), Keyword.t()) :: [tuple] 10 | def tokenize(prefix, options \\ []) do 11 | options = options |> Keyword.put(:emit_warnings, false) 12 | 13 | prefix 14 | |> String.to_charlist() 15 | |> do_tokenize(options) 16 | end 17 | 18 | defp do_tokenize(prefix_charlist, options) do 19 | result = 20 | if Version.match?(System.version(), ">= 1.14.0-dev") do 21 | :elixir_tokenizer.tokenize(prefix_charlist, 1, options) 22 | else 23 | # on 1.13 use our version as it has all the fixes from last 1.13 release 24 | :elixir_sense_tokenizer.tokenize(prefix_charlist, 1, options) 25 | end 26 | 27 | case result do 28 | # < 1.17 29 | {:ok, _line, _column, _warning, tokens} -> 30 | Enum.reverse(tokens) 31 | 32 | # >= 1.17 33 | {:ok, _line, _column, _warning, tokens, _terminators} -> 34 | tokens 35 | 36 | {:error, _, _, _, sofar} -> 37 | sofar 38 | end 39 | rescue 40 | e -> 41 | if Version.match?(System.version(), ">= 1.18.0-dev") do 42 | Logger.error( 43 | ":elixir_tokenizer.tokenize raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 44 | ) 45 | 46 | reraise e, __STACKTRACE__ 47 | else 48 | [] 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/normalized/typespec.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Normalized.Typespec do 2 | @moduledoc """ 3 | A module wrapping internal Elixir Code.Typespec APIs 4 | """ 5 | require Logger 6 | 7 | @spec get_specs(module) :: [tuple] 8 | def get_specs(module) do 9 | get_module().fetch_specs(module) 10 | |> extract_specs 11 | rescue 12 | e -> 13 | # workaround for crash 14 | # Keyword.fetch({:error, :beam_lib, {:not_a_beam_file, ""}}, :module) 15 | # fixed in elixir 1.16.0 16 | if Version.match?(System.version(), ">= 1.18.0-dev") do 17 | Logger.error( 18 | "Code.Typespec.fetch_specs raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 19 | ) 20 | 21 | reraise e, __STACKTRACE__ 22 | else 23 | [] 24 | end 25 | end 26 | 27 | @spec get_types(module) :: [tuple] 28 | def get_types(module) when is_atom(module) do 29 | get_module().fetch_types(module) 30 | |> extract_specs 31 | rescue 32 | e -> 33 | # workaround for crash 34 | # Keyword.fetch({:error, :beam_lib, {:not_a_beam_file, ""}}, :module) 35 | # fixed in elixir 1.16.0 36 | if Version.match?(System.version(), ">= 1.18.0-dev") do 37 | Logger.error( 38 | "Code.Typespec.fetch_types raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 39 | ) 40 | 41 | reraise e, __STACKTRACE__ 42 | else 43 | [] 44 | end 45 | end 46 | 47 | @spec get_callbacks(module) :: [tuple] 48 | def get_callbacks(module) do 49 | get_module().fetch_callbacks(module) 50 | |> extract_specs 51 | rescue 52 | e -> 53 | # workaround for crash 54 | # Keyword.fetch({:error, :beam_lib, {:not_a_beam_file, ""}}, :module) 55 | # fixed in elixir 1.16.0 56 | if Version.match?(System.version(), ">= 1.18.0-dev") do 57 | Logger.error( 58 | "Code.Typespec.fetch_callbacks raised #{Exception.format(:error, e, __STACKTRACE__)}. Please report that to elixir project." 59 | ) 60 | 61 | reraise e, __STACKTRACE__ 62 | else 63 | [] 64 | end 65 | end 66 | 67 | defp extract_specs({:ok, specs}), do: specs 68 | defp extract_specs(_), do: [] 69 | 70 | @spec type_to_quoted(tuple) :: Macro.t() 71 | def type_to_quoted(type) do 72 | get_module().type_to_quoted(type) 73 | end 74 | 75 | @spec spec_to_quoted(atom, tuple) :: {atom, keyword, [Macro.t()]} 76 | def spec_to_quoted(name, spec) do 77 | get_module().spec_to_quoted(name, spec) 78 | end 79 | 80 | defp get_module() do 81 | if Version.match?(System.version(), ">= 1.14.0-dev") do 82 | Code.Typespec 83 | else 84 | # on 1.13 use our version as it has all the fixes from last 1.13 release 85 | ElixirSense.Core.Normalized.Code.ElixirSense.Typespec 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/reserved_words.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.ReservedWords do 2 | @moduledoc """ 3 | Provides docs on reserved words 4 | """ 5 | 6 | @atoms ~w(true false nil)a 7 | @operators ~w(when and or not in)a 8 | @anonymous_function_definitions ~w(fn)a 9 | @do_end_blocks ~w(do end catch rescue after else)a 10 | 11 | @all @atoms ++ @operators ++ @anonymous_function_definitions ++ @do_end_blocks 12 | 13 | def all, do: @all 14 | 15 | def docs(keyword) when keyword in [true, false] do 16 | "Boolean value atom" 17 | end 18 | 19 | def docs(nil) do 20 | "nil value atom" 21 | end 22 | 23 | def docs(:fn) do 24 | "Anonymous function definition" 25 | end 26 | 27 | def docs(keyword) when keyword in [:and, :or, :not] do 28 | "Strict boolean operator" 29 | end 30 | 31 | def docs(:when) do 32 | "Guard expression operator" 33 | end 34 | 35 | def docs(:in) do 36 | "Membership check operator" 37 | end 38 | 39 | def docs(keyword) when keyword in @do_end_blocks do 40 | "do-end block control keyword" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/attribute_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.AttributeInfo do 2 | @moduledoc """ 3 | Variable info 4 | """ 5 | @type t :: %ElixirSense.Core.State.AttributeInfo{ 6 | name: atom, 7 | positions: list(ElixirSense.Core.Compiler.State.position_t()), 8 | type: ElixirSense.Core.Compiler.State.var_type() 9 | } 10 | defstruct name: nil, positions: [], type: nil 11 | end 12 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/call_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.CallInfo do 2 | @moduledoc """ 3 | Reference info 4 | """ 5 | @type t :: %ElixirSense.Core.State.CallInfo{ 6 | arity: non_neg_integer | nil, 7 | position: {non_neg_integer, pos_integer | nil}, 8 | func: atom, 9 | mod: module | {:attribute, atom}, 10 | kind: atom 11 | } 12 | defstruct arity: 0, 13 | position: {1, 1}, 14 | func: nil, 15 | mod: Elixir, 16 | kind: nil 17 | end 18 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/env.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.Env do 2 | @moduledoc """ 3 | Line environment 4 | """ 5 | 6 | @type t :: %ElixirSense.Core.State.Env{ 7 | functions: [{module, [{atom, arity}]}], 8 | macros: [{module, [{atom, arity}]}], 9 | requires: list(module), 10 | aliases: list(ElixirSense.Core.Compiler.State.alias_t()), 11 | macro_aliases: [{module, {term, module}}], 12 | context: nil | :match | :guard, 13 | module: nil | module, 14 | function: nil | {atom, arity}, 15 | protocol: nil | ElixirSense.Core.Compiler.State.protocol_t(), 16 | versioned_vars: %{optional({atom, atom}) => non_neg_integer}, 17 | vars: list(ElixirSense.Core.State.VarInfo.t()), 18 | attributes: list(ElixirSense.Core.State.AttributeInfo.t()), 19 | behaviours: list(module), 20 | context_modules: list(module), 21 | typespec: nil | {atom, arity}, 22 | scope_id: nil | ElixirSense.Core.Compiler.State.scope_id_t() 23 | } 24 | defstruct functions: [], 25 | macros: [], 26 | requires: [], 27 | aliases: [], 28 | macro_aliases: [], 29 | # NOTE for protocol implementation this will be the first variant 30 | module: nil, 31 | function: nil, 32 | # NOTE for protocol implementation this will be the first variant 33 | protocol: nil, 34 | versioned_vars: %{}, 35 | vars: [], 36 | attributes: [], 37 | behaviours: [], 38 | context_modules: [], 39 | context: nil, 40 | typespec: nil, 41 | scope_id: nil 42 | 43 | def to_macro_env(%__MODULE__{} = env, file \\ "nofile", line \\ 1) do 44 | # we omit lexical_tracker and tracers 45 | %Macro.Env{ 46 | line: line, 47 | file: file, 48 | context: env.context, 49 | module: env.module, 50 | function: env.function, 51 | context_modules: env.context_modules, 52 | macros: env.macros, 53 | functions: env.functions, 54 | requires: env.requires, 55 | aliases: env.aliases, 56 | macro_aliases: env.macro_aliases, 57 | versioned_vars: env.versioned_vars 58 | } 59 | end 60 | 61 | def update_from_macro_env(%__MODULE__{} = env, macro_env = %Macro.Env{}) do 62 | # we omit lexical_tracker and tracers 63 | %__MODULE__{ 64 | env 65 | | context: macro_env.context, 66 | module: macro_env.module, 67 | function: macro_env.function, 68 | context_modules: macro_env.context_modules, 69 | macros: macro_env.macros, 70 | functions: macro_env.functions, 71 | requires: macro_env.requires, 72 | aliases: macro_env.aliases, 73 | macro_aliases: macro_env.macro_aliases, 74 | versioned_vars: macro_env.versioned_vars 75 | } 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/mod_fun_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.ModFunInfo do 2 | @moduledoc """ 3 | Module or function info 4 | """ 5 | alias ElixirSense.Core.State.ModFunInfo 6 | alias ElixirSense.Core.Introspection 7 | 8 | @type t :: %ElixirSense.Core.State.ModFunInfo{ 9 | params: list(list(term)), 10 | positions: list(ElixirSense.Core.Compiler.State.position_t()), 11 | end_positions: list(ElixirSense.Core.Compiler.State.position_t() | nil), 12 | target: nil | {module, atom}, 13 | overridable: false | {true, module}, 14 | generated: list(boolean), 15 | doc: String.t(), 16 | meta: map(), 17 | # TODO defmodule defprotocol defimpl? 18 | type: 19 | :def 20 | | :defp 21 | | :defmacro 22 | | :defmacrop 23 | | :defdelegate 24 | | :defguard 25 | | :defguardp 26 | | :defmodule 27 | } 28 | 29 | defstruct params: [], 30 | positions: [], 31 | end_positions: [], 32 | target: nil, 33 | type: nil, 34 | generated: [], 35 | overridable: false, 36 | doc: "", 37 | meta: %{} 38 | 39 | def get_arities(%ModFunInfo{params: params_variants}) do 40 | params_variants 41 | |> Enum.map(fn params -> 42 | {length(params), Introspection.count_defaults(params)} 43 | end) 44 | end 45 | 46 | def get_category(%ModFunInfo{type: type}) 47 | when type in [:defmacro, :defmacrop, :defguard, :defguardp], 48 | do: :macro 49 | 50 | def get_category(%ModFunInfo{type: type}) when type in [:def, :defp, :defdelegate], 51 | do: :function 52 | 53 | def get_category(%ModFunInfo{}), do: :module 54 | 55 | def private?(%ModFunInfo{type: type}), do: type in [:defp, :defmacrop, :defguardp] 56 | 57 | def get_def_kind(%ModFunInfo{type: type}) 58 | when type in [:defmacro, :defguard], 59 | do: :defmacro 60 | 61 | def get_def_kind(%ModFunInfo{type: type}) 62 | when type in [:defmacrop, :defguardp], 63 | do: :defmacrop 64 | 65 | def get_def_kind(%ModFunInfo{type: type}) when type in [:def, :defdelegate], 66 | do: :def 67 | 68 | def get_def_kind(%ModFunInfo{type: type}) when type in [:defp], 69 | do: :defp 70 | end 71 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/record_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.RecordInfo do 2 | @moduledoc """ 3 | Record definition info 4 | """ 5 | @type field_t :: {atom, any} 6 | @type t :: %ElixirSense.Core.State.RecordInfo{ 7 | type: :defrecord | :defrecordp, 8 | fields: list(field_t), 9 | doc: String.t(), 10 | meta: map() 11 | } 12 | defstruct type: :defrecord, fields: [], doc: "", meta: %{} 13 | end 14 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/spec_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.SpecInfo do 2 | @moduledoc """ 3 | Type definition info 4 | """ 5 | @type t :: %ElixirSense.Core.State.SpecInfo{ 6 | name: atom, 7 | args: list(list(String.t())), 8 | specs: [String.t()], 9 | kind: :spec | :callback | :macrocallback, 10 | positions: [ElixirSense.Core.Compiler.State.position_t()], 11 | end_positions: [ElixirSense.Core.Compiler.State.position_t() | nil], 12 | doc: String.t(), 13 | meta: map(), 14 | generated: list(boolean) 15 | } 16 | defstruct name: nil, 17 | args: [], 18 | specs: [], 19 | kind: :spec, 20 | positions: [], 21 | end_positions: [], 22 | generated: [], 23 | doc: "", 24 | meta: %{} 25 | end 26 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/struct_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.StructInfo do 2 | @moduledoc """ 3 | Structure definition info 4 | """ 5 | @type field_t :: {atom, any} 6 | @type t :: %ElixirSense.Core.State.StructInfo{ 7 | type: :defstruct | :defexception, 8 | fields: list(field_t), 9 | doc: String.t(), 10 | meta: map() 11 | } 12 | defstruct type: :defstruct, fields: [], doc: "", meta: %{} 13 | end 14 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/type_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.TypeInfo do 2 | @moduledoc """ 3 | Type definition info 4 | """ 5 | @type t :: %ElixirSense.Core.State.TypeInfo{ 6 | name: atom, 7 | args: list(list(String.t())), 8 | specs: [String.t()], 9 | kind: :type | :typep | :opaque, 10 | positions: [ElixirSense.Core.Compiler.State.position_t()], 11 | end_positions: [ElixirSense.Core.Compiler.State.position_t() | nil], 12 | doc: String.t(), 13 | meta: map(), 14 | generated: list(boolean) 15 | } 16 | defstruct name: nil, 17 | args: [], 18 | specs: [], 19 | kind: :type, 20 | positions: [], 21 | end_positions: [], 22 | generated: [], 23 | doc: "", 24 | meta: %{} 25 | end 26 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/state/var_info.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.State.VarInfo do 2 | @moduledoc """ 3 | Variable info 4 | """ 5 | 6 | @type t :: %ElixirSense.Core.State.VarInfo{ 7 | name: atom, 8 | positions: list(ElixirSense.Core.Compiler.State.position_t()), 9 | scope_id: nil | ElixirSense.Core.Compiler.State.scope_id_t(), 10 | version: non_neg_integer(), 11 | type: ElixirSense.Core.Compiler.State.var_type() 12 | } 13 | defstruct name: nil, 14 | positions: [], 15 | scope_id: nil, 16 | version: 0, 17 | type: nil 18 | end 19 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/struct.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Struct do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Introspection 5 | alias ElixirSense.Core.State 6 | 7 | @spec is_struct(module | nil, ElixirSense.Core.Compiler.State.structs_t()) :: boolean 8 | def is_struct(nil, _metadata_structs), do: false 9 | 10 | def is_struct(module, metadata_structs) do 11 | Map.has_key?(metadata_structs, module) or Introspection.module_is_struct?(module) 12 | end 13 | 14 | @spec get_fields(module, ElixirSense.Core.Compiler.State.structs_t()) :: [atom] 15 | def get_fields(module, metadata_structs) do 16 | case metadata_structs[module] do 17 | %State.StructInfo{fields: fields} -> 18 | Enum.map(fields, &(&1 |> elem(0))) 19 | 20 | nil -> 21 | try do 22 | module 23 | |> struct() 24 | |> Map.from_struct() 25 | |> Map.keys() 26 | |> Kernel.++([:__struct__]) 27 | rescue 28 | _ -> 29 | # return dummy in case struct/1 fails 30 | [:__struct__] 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/surround_context.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.SurroundContext do 2 | @moduledoc false 3 | 4 | def to_binding(item, current_module) do 5 | try do 6 | to_binding_impl(item, current_module) 7 | rescue 8 | _e in SystemLimitError -> 9 | nil 10 | end 11 | end 12 | 13 | defp to_binding_impl({:alias, charlist}, _current_module) do 14 | {{:atom, :"Elixir.#{charlist}"}, nil} 15 | end 16 | 17 | # do not handle any other local_or_var 18 | defp to_binding_impl({:alias, {:local_or_var, ~c"__MODULE__"}, charlist}, current_module) do 19 | if current_module != nil do 20 | {{:atom, :"#{current_module}.#{charlist}"}, nil} 21 | end 22 | end 23 | 24 | defp to_binding_impl({:alias, {:local_or_var, _charlist1}, _charlist}, _current_module), do: nil 25 | 26 | # TODO handle this case? 27 | defp to_binding_impl({:alias, {:module_attribute, _charlist1}, _charlist}, _current_module), 28 | do: nil 29 | 30 | # this probably only existed on 1.14 31 | defp to_binding_impl({:alias, {:dot, _, _}, _charlist}, _current_module), 32 | do: nil 33 | 34 | defp to_binding_impl({:dot, inside_dot, charlist}, current_module) do 35 | {inside_dot_to_binding(inside_dot, current_module), :"#{charlist}"} 36 | end 37 | 38 | defp to_binding_impl({:local_or_var, ~c"__MODULE__"}, current_module) do 39 | if current_module != nil do 40 | {{:atom, current_module}, nil} 41 | end 42 | end 43 | 44 | defp to_binding_impl({:local_or_var, charlist}, _current_module) do 45 | {:variable, :"#{charlist}", :any} 46 | end 47 | 48 | defp to_binding_impl({:capture_arg, charlist}, _current_module) do 49 | {:variable, :"#{charlist}", :any} 50 | end 51 | 52 | defp to_binding_impl({:local_arity, charlist}, _current_module) do 53 | {nil, :"#{charlist}"} 54 | end 55 | 56 | defp to_binding_impl({:local_call, charlist}, _current_module) do 57 | {nil, :"#{charlist}"} 58 | end 59 | 60 | defp to_binding_impl({:module_attribute, charlist}, _current_module) do 61 | {:attribute, :"#{charlist}"} 62 | end 63 | 64 | defp to_binding_impl({:operator, charlist}, _current_module) do 65 | {nil, :"#{charlist}"} 66 | end 67 | 68 | defp to_binding_impl({:sigil, charlist}, _current_module) do 69 | {nil, :"sigil_#{charlist}"} 70 | end 71 | 72 | defp to_binding_impl({:struct, charlist}, _current_module) when is_list(charlist) do 73 | {{:atom, :"Elixir.#{charlist}"}, nil} 74 | end 75 | 76 | # handles 77 | # {:alias, inside_alias, charlist} 78 | # {:local_or_var, charlist} 79 | # {:module_attribute, charlist} 80 | # {:dot, inside_dot, charlist} 81 | defp to_binding_impl({:struct, inside_struct}, current_module) do 82 | to_binding_impl(inside_struct, current_module) 83 | end 84 | 85 | defp to_binding_impl({:unquoted_atom, charlist}, _current_module) do 86 | {{:atom, :"#{charlist}"}, nil} 87 | end 88 | 89 | defp to_binding_impl({:key, charlist}, _current_module) do 90 | {{:atom, :"#{charlist}"}, nil} 91 | end 92 | 93 | defp to_binding_impl({:keyword, charlist}, _current_module) do 94 | {:keyword, :"#{charlist}"} 95 | end 96 | 97 | defp inside_dot_to_binding({:alias, inside_charlist}, _current_module) 98 | when is_list(inside_charlist) do 99 | {:atom, :"Elixir.#{inside_charlist}"} 100 | end 101 | 102 | defp inside_dot_to_binding( 103 | {:alias, {:local_or_var, ~c"__MODULE__"}, inside_charlist}, 104 | current_module 105 | ) do 106 | if current_module != nil do 107 | {:atom, :"#{current_module |> Atom.to_string()}.#{inside_charlist}"} 108 | end 109 | end 110 | 111 | # TODO handle {:alias, {:module_attribute, charlist1}, charlist}? 112 | defp inside_dot_to_binding({:alias, _other, _inside_charlist}, _current_module) do 113 | nil 114 | end 115 | 116 | defp inside_dot_to_binding({:dot, inside_dot, inside_charlist}, current_module) do 117 | {:call, inside_dot_to_binding(inside_dot, current_module), :"#{inside_charlist}", []} 118 | end 119 | 120 | defp inside_dot_to_binding({:module_attribute, inside_charlist}, _current_module) do 121 | {:attribute, :"#{inside_charlist}"} 122 | end 123 | 124 | defp inside_dot_to_binding({:unquoted_atom, inside_charlist}, _current_module) do 125 | {:atom, :"#{inside_charlist}"} 126 | end 127 | 128 | defp inside_dot_to_binding({:var, ~c"__MODULE__"}, current_module) do 129 | if current_module != nil do 130 | {:atom, current_module} 131 | end 132 | end 133 | 134 | defp inside_dot_to_binding({:var, inside_charlist}, _current_module) do 135 | {:variable, :"#{inside_charlist}", :any} 136 | end 137 | 138 | defp inside_dot_to_binding(:expr, _current_module) do 139 | nil 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/elixir_sense/core/type_ast.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.TypeAst do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Normalized.Typespec 5 | 6 | def from_typedef({_kind, type}) do 7 | Typespec.type_to_quoted(type) 8 | end 9 | 10 | def extract_signature(nil) do 11 | nil 12 | end 13 | 14 | def extract_signature(ast) do 15 | ast 16 | |> extract_spec_ast_parts 17 | |> Map.get(:name) 18 | |> Macro.to_string() 19 | end 20 | 21 | defp extract_spec_ast_parts({:when, _, [{:"::", _, [name_part, return_part]}, when_part]}) do 22 | %{name: name_part, returns: extract_return_part(return_part, []), when_part: when_part} 23 | end 24 | 25 | defp extract_spec_ast_parts({:"::", _, [name_part, return_part]}) do 26 | %{name: name_part, returns: extract_return_part(return_part, [])} 27 | end 28 | 29 | defp extract_return_part({:|, _, [lhs, rhs]}, returns) do 30 | [lhs | extract_return_part(rhs, returns)] 31 | end 32 | 33 | defp extract_return_part(ast, returns) do 34 | [ast | returns] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/elixir_sense/log.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Log do 2 | @moduledoc """ 3 | A simple logger for the project that allows it to be muted via application config 4 | """ 5 | require Logger 6 | 7 | def enabled? do 8 | Application.get_env(:elixir_sense, :logging_enabled, true) 9 | end 10 | 11 | defmacro info(message) do 12 | quote do 13 | require Logger 14 | 15 | if ElixirSense.Log.enabled?() do 16 | Logger.info(unquote(message)) 17 | end 18 | end 19 | end 20 | 21 | defmacro warn(message) do 22 | quote do 23 | require Logger 24 | 25 | if ElixirSense.Log.enabled?() do 26 | Logger.warning(unquote(message)) 27 | end 28 | end 29 | end 30 | 31 | defmacro error(message) when is_binary(message) do 32 | quote do 33 | require Logger 34 | 35 | if ElixirSense.Log.enabled?() do 36 | Logger.error(unquote(message)) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/generic_reducer.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.GenericReducer do 2 | @moduledoc """ 3 | A generic behaviour for reducers that customize suggestions 4 | according to the cursor's position in a function call. 5 | """ 6 | 7 | require Logger 8 | alias ElixirSense.Providers.Plugins.Util 9 | 10 | @type func_call :: {module, fun :: atom, arg :: non_neg_integer, any} 11 | @type suggestion :: ElixirSense.Providers.Completion.Suggestion.generic() 12 | @type reducer_name :: atom() 13 | 14 | @callback suggestions(hint :: String.t(), func_call, [func_call], opts :: map) :: 15 | :ignore 16 | | {:add | :override, [suggestion]} 17 | | {:add | :override, [suggestion], [reducer_name]} 18 | 19 | defmacro __using__(_) do 20 | quote do 21 | @behaviour unquote(__MODULE__) 22 | 23 | def reduce(hint, env, buffer_metadata, cursor_context, acc) do 24 | unquote(__MODULE__).reduce(__MODULE__, hint, env, buffer_metadata, cursor_context, acc) 25 | end 26 | end 27 | end 28 | 29 | def reduce(reducer, hint, env, buffer_metadata, cursor_context, acc) do 30 | text_before = cursor_context.text_before 31 | 32 | opts = %{ 33 | env: env, 34 | buffer_metadata: buffer_metadata, 35 | cursor_context: cursor_context, 36 | module_store: acc.context.module_store 37 | } 38 | 39 | case Util.func_call_chain(text_before, env, buffer_metadata, cursor_context.cursor_position) do 40 | [func_call | _] = chain -> 41 | if function_exported?(reducer, :suggestions, 4) do 42 | try do 43 | reducer.suggestions(hint, func_call, chain, opts) |> handle_suggestions(acc) 44 | catch 45 | kind, payload -> 46 | {payload, stacktrace} = Exception.blame(kind, payload, __STACKTRACE__) 47 | message = Exception.format(kind, payload, stacktrace) 48 | Logger.error("Error in suggestions reducer: #{message}") 49 | {:cont, acc} 50 | end 51 | else 52 | {:cont, acc} 53 | end 54 | 55 | [] -> 56 | if function_exported?(reducer, :suggestions, 2) do 57 | try do 58 | reducer.suggestions(hint, opts) |> handle_suggestions(acc) 59 | catch 60 | kind, payload -> 61 | {payload, stacktrace} = Exception.blame(kind, payload, __STACKTRACE__) 62 | message = Exception.format(kind, payload, stacktrace) 63 | Logger.error("Error in suggestions reducer: #{message}") 64 | {:cont, acc} 65 | end 66 | else 67 | {:cont, acc} 68 | end 69 | end 70 | end 71 | 72 | def handle_suggestions(:ignore, acc) do 73 | {:cont, acc} 74 | end 75 | 76 | def handle_suggestions({:add, suggestions}, acc) do 77 | {:cont, %{acc | result: suggestions ++ acc.result}} 78 | end 79 | 80 | def handle_suggestions({:add, suggestions, reducers}, acc) do 81 | {:cont, %{acc | result: suggestions ++ acc.result, reducers: reducers}} 82 | end 83 | 84 | def handle_suggestions({:override, suggestions}, acc) do 85 | {:halt, %{acc | result: suggestions}} 86 | end 87 | 88 | def handle_suggestions({:override, suggestions, reducers}, acc) do 89 | {:cont, %{acc | result: suggestions, reducers: reducers}} 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducer.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducer do 2 | @moduledoc !""" 3 | Provides common functions for reducers. 4 | """ 5 | 6 | def put_context(acc, key, value) do 7 | updated_context = Map.put(acc.context, key, value) 8 | put_in(acc.context, updated_context) 9 | end 10 | 11 | def get_context(acc, key) do 12 | get_in(acc, [:context, key]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/bitstring.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.Bitstring do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Bitstring 5 | alias ElixirSense.Core.Source 6 | 7 | @type bitstring_option :: %{ 8 | type: :bitstring_option, 9 | name: String.t() 10 | } 11 | 12 | @doc """ 13 | A reducer that adds suggestions of bitstring options. 14 | """ 15 | def add_bitstring_options(_hint, _env, _buffer_metadata, cursor_context, acc) do 16 | prefix = cursor_context.text_before 17 | 18 | case Source.bitstring_options(prefix) do 19 | candidate when not is_nil(candidate) -> 20 | parsed = Bitstring.parse(candidate) 21 | 22 | list = 23 | for option <- Bitstring.available_options(parsed), 24 | candidate_part = candidate |> String.split("-") |> List.last(), 25 | option_str = option |> Atom.to_string(), 26 | String.starts_with?(option_str, candidate_part) do 27 | %{ 28 | name: option_str, 29 | type: :bitstring_option 30 | } 31 | end 32 | 33 | {:cont, %{acc | result: acc.result ++ list}} 34 | 35 | _ -> 36 | {:cont, acc} 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/complete_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.CompleteEngine do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Metadata 5 | alias ElixirSense.Core.Source 6 | alias ElixirSense.Core.State 7 | alias ElixirSense.Providers.Completion.CompletionEngine 8 | alias ElixirSense.Providers.Completion.Reducer 9 | 10 | @type t() :: CompletionEngine.t() 11 | 12 | @doc """ 13 | A reducer that populates the context with the suggestions provided by 14 | the `ElixirLS.Utils.CompletionEngine` module. 15 | 16 | The suggestions are grouped by type and saved in the context under the 17 | `:complete_engine_suggestions_by_type` key and can be accessed by any reducer 18 | that runs after. 19 | 20 | Available suggestions: 21 | 22 | * Modules 23 | * Functions 24 | * Macros 25 | * Variables 26 | * Module attributes 27 | * Variable fields 28 | 29 | """ 30 | def populate(hint, env, buffer_metadata, context, acc, opts \\ []) do 31 | suggestions = 32 | find_mods_funcs( 33 | hint, 34 | context.cursor_position, 35 | env, 36 | buffer_metadata, 37 | context.text_before, 38 | opts 39 | ) 40 | 41 | suggestions_by_type = Enum.group_by(suggestions, & &1.type) 42 | 43 | {:cont, Reducer.put_context(acc, :complete_engine, suggestions_by_type)} 44 | end 45 | 46 | @doc """ 47 | A reducer that adds suggestions of existing modules. 48 | 49 | Note: requires populate/5. 50 | """ 51 | def add_modules(_hint, _env, _file_metadata, _context, acc) do 52 | add_suggestions(:module, acc) 53 | end 54 | 55 | @doc """ 56 | A reducer that adds suggestions of existing functions. 57 | 58 | Note: requires populate/5. 59 | """ 60 | def add_functions(_hint, _env, _file_metadata, _context, acc) do 61 | add_suggestions(:function, acc) 62 | end 63 | 64 | @doc """ 65 | A reducer that adds suggestions of existing macros. 66 | 67 | Note: requires populate/5. 68 | """ 69 | def add_macros(_hint, _env, _file_metadata, _context, acc) do 70 | add_suggestions(:macro, acc) 71 | end 72 | 73 | @doc """ 74 | A reducer that adds suggestions of variable fields. 75 | 76 | Note: requires populate/5. 77 | """ 78 | def add_fields(_hint, _env, _file_metadata, _context, acc) do 79 | add_suggestions(:field, acc) 80 | end 81 | 82 | @doc """ 83 | A reducer that adds suggestions of existing module attributes. 84 | 85 | Note: requires populate/5. 86 | """ 87 | def add_attributes(_hint, _env, _file_metadata, _context, acc) do 88 | add_suggestions(:attribute, acc) 89 | end 90 | 91 | @doc """ 92 | A reducer that adds suggestions of existing variables. 93 | 94 | Note: requires populate/5. 95 | """ 96 | def add_variables(_hint, _env, _file_metadata, _context, acc) do 97 | add_suggestions(:variable, acc) 98 | end 99 | 100 | defp add_suggestions(type, acc) do 101 | suggestions_by_type = Reducer.get_context(acc, :complete_engine) 102 | list = Map.get(suggestions_by_type, type, []) 103 | {:cont, %{acc | result: acc.result ++ list}} 104 | end 105 | 106 | defp find_mods_funcs( 107 | hint, 108 | cursor_position, 109 | %State.Env{ 110 | module: module 111 | } = env, 112 | %Metadata{} = metadata, 113 | text_before, 114 | opts 115 | ) do 116 | hint = 117 | case Source.get_v12_module_prefix(text_before, module) do 118 | nil -> 119 | hint 120 | 121 | module_string -> 122 | # multi alias syntax detected 123 | # prepend module prefix before running completion 124 | prefix = module_string <> "." 125 | prefix <> hint 126 | end 127 | 128 | hint = 129 | if String.starts_with?(hint, "__MODULE__") do 130 | hint |> String.replace_leading("__MODULE__", inspect(module)) 131 | else 132 | hint 133 | end 134 | 135 | CompletionEngine.complete(hint, env, metadata, cursor_position, opts) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/docs_snippets.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.DocsSnippets do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Providers.Plugins.Util 5 | alias ElixirSense.Providers.Utils.Matcher 6 | 7 | # Format: 8 | # {label, snippet, documentation, priority} 9 | @module_attr_snippets [ 10 | {~s(@doc """"""), ~s(@doc """\n$0\n"""), "Documents a function/macro/callback", 13}, 11 | {"@doc false", "@doc false", "Marks this function/macro/callback as internal", 15}, 12 | {~s(@moduledoc """"""), ~s(@moduledoc """\n$0\n"""), "Documents a module", 13}, 13 | {"@moduledoc false", "@moduledoc false", "Marks this module as internal", 15}, 14 | {~s(@typedoc """"""), ~s(@typedoc """\n$0\n"""), "Documents a type specification", 13}, 15 | {"@typedoc false", "@typedoc false", "Marks this type specification as internal", 15} 16 | ] 17 | 18 | @doc """ 19 | A reducer that adds suggestions for @doc, @moduledoc and @typedoc. 20 | """ 21 | def add_snippets(hint, _env, _metadata, %{at_module_body?: true}, acc) do 22 | list = 23 | for {label, snippet, doc, priority} <- @module_attr_snippets, 24 | Matcher.match?(label, hint) do 25 | %{ 26 | type: :generic, 27 | kind: :snippet, 28 | label: label, 29 | snippet: Util.trim_leading_for_insertion(hint, snippet), 30 | filter_text: String.replace_prefix(label, "@", "") |> String.split(" ") |> List.first(), 31 | detail: "module attribute snippet", 32 | documentation: doc, 33 | priority: priority 34 | } 35 | end 36 | 37 | {:cont, %{acc | result: acc.result ++ Enum.sort(list)}} 38 | end 39 | 40 | def add_snippets(_hint, _env, _metadata, _cursor_context, acc), 41 | do: {:cont, acc} 42 | end 43 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/overridable.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.Overridable do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Introspection 5 | alias ElixirSense.Core.State 6 | alias ElixirSense.Providers.Utils.Matcher 7 | 8 | @doc """ 9 | A reducer that adds suggestions of overridable functions. 10 | """ 11 | 12 | def add_overridable( 13 | hint, 14 | env = %State.Env{typespec: nil, module: module}, 15 | metadata, 16 | _cursor_context, 17 | acc 18 | ) 19 | when not is_nil(module) do 20 | %State.Env{protocol: protocol, behaviours: behaviours, module: module} = env 21 | 22 | # overridable behaviour callbacks are returned by Reducers.Callbacks 23 | behaviour_callbacks = 24 | Enum.flat_map(behaviours, fn 25 | mod when is_atom(mod) and (protocol == nil or mod != elem(protocol, 0)) -> 26 | for %{ 27 | name: name, 28 | arity: arity 29 | } <- 30 | Introspection.get_callbacks_with_docs(mod) do 31 | {name, arity} 32 | end 33 | 34 | _ -> 35 | [] 36 | end) 37 | 38 | # no need to care of default args here 39 | # only the max arity version can be overridden 40 | list = 41 | for {{^module, name, arity}, %State.ModFunInfo{overridable: {true, origin}} = info} 42 | when is_integer(arity) <- metadata.mods_funs_to_positions, 43 | def_prefix?(hint, info.type) or Matcher.match?("#{name}", hint), 44 | {name, arity} not in behaviour_callbacks do 45 | spec = 46 | case metadata.specs[{module, name, arity}] do 47 | %State.SpecInfo{specs: specs} -> specs |> Enum.join("\n") 48 | nil -> "" 49 | end 50 | 51 | args_list = 52 | info.params 53 | |> List.last() 54 | |> Enum.with_index() 55 | |> Enum.map(&Introspection.param_to_var/1) 56 | 57 | args = args_list |> Enum.join(", ") 58 | 59 | subtype = 60 | case State.ModFunInfo.get_category(info) do 61 | :function -> :callback 62 | :macro -> :macrocallback 63 | end 64 | 65 | %{ 66 | type: :callback, 67 | subtype: subtype, 68 | name: Atom.to_string(name), 69 | arity: arity, 70 | args: args, 71 | args_list: args_list, 72 | origin: inspect(origin), 73 | summary: Introspection.extract_summary_from_docs(info.doc), 74 | metadata: info.meta, 75 | spec: spec 76 | } 77 | end 78 | 79 | {:cont, %{acc | result: acc.result ++ Enum.sort(list)}} 80 | end 81 | 82 | def add_overridable(_hint, %State.Env{}, _metadata, _cursor_context, acc), 83 | do: {:cont, acc} 84 | 85 | defp def_prefix?(hint, type) when type in [:defmacro, :defmacrop] do 86 | String.starts_with?("defmacro", hint) 87 | end 88 | 89 | defp def_prefix?(hint, type) when type in [:defguard, :defguardp] do 90 | String.starts_with?("defguard", hint) 91 | end 92 | 93 | defp def_prefix?(hint, type) when type in [:def, :defp, :defdelegate] do 94 | String.starts_with?("def", hint) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/params.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.Params do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Binding 5 | alias ElixirSense.Core.Introspection 6 | alias ElixirSense.Core.Metadata 7 | alias ElixirSense.Core.Source 8 | alias ElixirSense.Providers.Utils.Matcher 9 | 10 | @type param_option :: %{ 11 | type: :param_option, 12 | subtype: :keyword | :atom, 13 | name: String.t(), 14 | origin: String.t(), 15 | type_spec: String.t() 16 | } 17 | 18 | @doc """ 19 | A reducer that adds suggestions of keyword list options. 20 | """ 21 | def add_options(hint, env, buffer_metadata, cursor_context, acc) do 22 | prefix = cursor_context.text_before 23 | 24 | binding_env = Binding.from_env(env, buffer_metadata, cursor_context.cursor_position) 25 | 26 | %Metadata{mods_funs_to_positions: mods_funs, types: metadata_types} = buffer_metadata 27 | 28 | with %{ 29 | candidate: {mod, fun}, 30 | elixir_prefix: elixir_prefix, 31 | options_so_far: options_so_far, 32 | cursor_at_option: cursor_at_option, 33 | npar: npar 34 | } <- 35 | Source.which_func(prefix, binding_env), 36 | {mod, fun, true, :mod_fun} <- 37 | Introspection.actual_mod_fun( 38 | {mod, fun}, 39 | env, 40 | mods_funs, 41 | metadata_types, 42 | cursor_context.cursor_position, 43 | not elixir_prefix 44 | ) do 45 | list = 46 | for opt <- 47 | ElixirSense.Core.Options.get_param_options(mod, fun, npar + 1, env, buffer_metadata) do 48 | case opt do 49 | {name, type} -> 50 | # match on atom: 51 | if Matcher.match?(to_string(name) <> ":", hint) do 52 | expanded_spec = Introspection.to_string_with_parens(type) 53 | 54 | %{ 55 | name: name |> Atom.to_string(), 56 | origin: "#{inspect(mod)}.#{fun}", 57 | type: :param_option, 58 | subtype: :keyword, 59 | type_spec: expanded_spec 60 | } 61 | end 62 | 63 | name -> 64 | # match on :atom 65 | if options_so_far == [] and cursor_at_option == true and 66 | Matcher.match?(inspect(name), hint) do 67 | %{ 68 | name: name |> Atom.to_string(), 69 | origin: "#{inspect(mod)}.#{fun}", 70 | type: :param_option, 71 | subtype: :atom, 72 | type_spec: "" 73 | } 74 | end 75 | end 76 | end 77 | |> Enum.reject(&is_nil/1) 78 | 79 | {:cont, %{acc | result: acc.result ++ list}} 80 | else 81 | _ -> 82 | {:cont, acc} 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/protocol.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.Protocol do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Introspection 5 | alias ElixirSense.Core.State 6 | alias ElixirSense.Providers.Utils.Matcher 7 | 8 | @type protocol_function :: %{ 9 | type: :protocol_function, 10 | name: String.t(), 11 | arity: non_neg_integer, 12 | args: String.t(), 13 | args_list: [String.t()], 14 | origin: String.t(), 15 | summary: String.t(), 16 | spec: String.t(), 17 | metadata: map 18 | } 19 | 20 | @doc """ 21 | A reducer that adds suggestions of protocol functions. 22 | """ 23 | def add_functions(_hint, %State.Env{typespec: {_f, _a}}, _metadata, _cursor_context, acc), 24 | do: {:cont, acc} 25 | 26 | def add_functions(_hint, %State.Env{module: nil}, _metadata, _cursor_context, acc), 27 | do: {:cont, acc} 28 | 29 | def add_functions(_hint, %State.Env{protocol: nil}, _metadata, _cursor_context, acc), 30 | do: {:cont, acc} 31 | 32 | def add_functions(hint, env, buffer_metadata, _cursor_context, acc) do 33 | %State.Env{protocol: {protocol, _implementations}} = env 34 | 35 | mod_name = inspect(protocol) 36 | 37 | list = 38 | if Map.has_key?(buffer_metadata.mods_funs_to_positions, {protocol, nil, nil}) do 39 | behaviour_callbacks = 40 | buffer_metadata.specs 41 | |> Enum.filter(fn {{mod, _, arity}, %State.SpecInfo{kind: kind}} -> 42 | mod == protocol and is_integer(arity) and kind in [:callback] 43 | end) 44 | 45 | for {{_, name, arity}, %State.SpecInfo{} = info} <- behaviour_callbacks, 46 | hint == "" or String.starts_with?("def", hint) or Matcher.match?("#{name}", hint) do 47 | %State.ModFunInfo{} = 48 | def_info = 49 | buffer_metadata.mods_funs_to_positions |> Map.fetch!({protocol, name, arity}) 50 | 51 | %{ 52 | type: :protocol_function, 53 | name: Atom.to_string(name), 54 | arity: arity, 55 | args: Enum.join(List.last(info.args), ", "), 56 | args_list: List.last(info.args), 57 | origin: mod_name, 58 | summary: Introspection.extract_summary_from_docs(def_info.doc), 59 | spec: List.last(info.specs), 60 | metadata: def_info.meta 61 | } 62 | end 63 | else 64 | for {{name, arity}, {_type, args, docs, metadata, spec}} <- 65 | Introspection.module_functions_info(protocol), 66 | not match?(%{implementing: Protocol}, metadata), 67 | hint == "" or String.starts_with?("def", hint) or Matcher.match?("#{name}", hint) do 68 | %{ 69 | type: :protocol_function, 70 | name: Atom.to_string(name), 71 | arity: arity, 72 | args: args |> Enum.join(", "), 73 | args_list: args, 74 | origin: inspect(protocol), 75 | summary: docs, 76 | metadata: metadata, 77 | spec: spec 78 | } 79 | end 80 | end 81 | 82 | {:cont, %{acc | result: acc.result ++ Enum.sort(list)}} 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/returns.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.Returns do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Introspection 5 | alias ElixirSense.Core.Metadata 6 | alias ElixirSense.Core.State 7 | 8 | @type return :: %{ 9 | type: :return, 10 | description: String.t(), 11 | spec: String.t(), 12 | snippet: String.t() 13 | } 14 | 15 | @doc """ 16 | A reducer that adds suggestions of possible return values. 17 | """ 18 | def add_returns( 19 | "" = _hint, 20 | %State.Env{function: {fun, arity}} = env, 21 | buffer_metadata, 22 | _context, 23 | acc 24 | ) do 25 | %State.Env{module: current_module, behaviours: behaviours, protocol: protocol} = env 26 | %Metadata{specs: specs} = buffer_metadata 27 | 28 | spec_returns = 29 | case specs[{current_module, fun, arity}] do 30 | nil -> 31 | [] 32 | 33 | %State.SpecInfo{specs: info_specs} -> 34 | for spec <- info_specs, 35 | {:ok, {:@, _, [{_, _, [quoted]}]}} <- [ 36 | Code.string_to_quoted(spec, emit_warnings: false) 37 | ], 38 | return <- Introspection.get_returns_from_spec_ast(quoted) do 39 | format_return(return) 40 | end 41 | end 42 | 43 | callbacks = 44 | for mod <- behaviours, 45 | protocol == nil or mod != elem(protocol, 0) do 46 | case specs[{mod, fun, arity}] do 47 | nil -> 48 | for return <- Introspection.get_returns_from_callback(mod, fun, arity) do 49 | format_return(return) 50 | end 51 | 52 | %State.SpecInfo{specs: info_specs} -> 53 | for spec <- info_specs, 54 | {:ok, {:@, _, [{_, _, [quoted]}]}} <- [ 55 | Code.string_to_quoted(spec, emit_warnings: false) 56 | ], 57 | return <- Introspection.get_returns_from_spec_ast(quoted) do 58 | format_return(return) 59 | end 60 | end 61 | end 62 | |> List.flatten() 63 | 64 | protocol_functions = 65 | case protocol do 66 | {proto, _implementations} -> 67 | case specs[{proto, fun, arity}] do 68 | nil -> 69 | for return <- Introspection.get_returns_from_callback(proto, fun, arity) do 70 | format_return(return) 71 | end 72 | 73 | %State.SpecInfo{specs: info_specs} -> 74 | for spec <- info_specs, 75 | {:ok, {:@, _, [{:callback, _, [quoted]}]}} <- [ 76 | Code.string_to_quoted(spec, emit_warnings: false) 77 | ], 78 | return <- Introspection.get_returns_from_spec_ast(quoted) do 79 | format_return(return) 80 | end 81 | end 82 | 83 | nil -> 84 | [] 85 | end 86 | 87 | list = callbacks ++ protocol_functions ++ spec_returns 88 | {:cont, %{acc | result: acc.result ++ list}} 89 | end 90 | 91 | def add_returns(_hint, _env, _buffer_metadata, _context, acc) do 92 | {:cont, acc} 93 | end 94 | 95 | defp format_return(return) do 96 | %{ 97 | type: :return, 98 | description: return.description, 99 | spec: return.spec, 100 | snippet: return.snippet 101 | } 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/completion/reducers/struct.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Completion.Reducers.Struct do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Binding 5 | alias ElixirSense.Core.Introspection 6 | alias ElixirSense.Core.Metadata 7 | alias ElixirSense.Core.Source 8 | alias ElixirSense.Core.State 9 | alias ElixirSense.Providers.Utils.Matcher 10 | 11 | @type field :: %{ 12 | type: :field, 13 | subtype: :struct_field | :map_key, 14 | name: String.t(), 15 | origin: String.t() | nil, 16 | call?: boolean, 17 | type_spec: String.t() | nil 18 | } 19 | 20 | @doc """ 21 | A reducer that adds suggestions of struct fields. 22 | """ 23 | def add_fields(hint, env, buffer_metadata, context, acc) do 24 | text_before = context.text_before 25 | 26 | case find_struct_fields(hint, text_before, env, buffer_metadata, context.cursor_position) do 27 | {[], _} -> 28 | {:cont, acc} 29 | 30 | {fields, nil} -> 31 | {:halt, %{acc | result: fields}} 32 | 33 | {fields, :maybe_struct_update} -> 34 | reducers = [ 35 | :populate_complete_engine, 36 | :modules, 37 | :functions, 38 | :macros, 39 | :variables, 40 | :attributes 41 | ] 42 | 43 | {:cont, %{acc | result: fields, reducers: reducers}} 44 | end 45 | end 46 | 47 | defp find_struct_fields( 48 | hint, 49 | text_before, 50 | %State.Env{ 51 | module: module, 52 | aliases: aliases 53 | } = env, 54 | %Metadata{} = buffer_metadata, 55 | cursor_position 56 | ) do 57 | binding_env = ElixirSense.Core.Binding.from_env(env, buffer_metadata, cursor_position) 58 | 59 | case Source.which_struct(text_before, module) do 60 | {type, fields_so_far, elixir_prefix, var} -> 61 | type = 62 | case {type, elixir_prefix} do 63 | {{:atom, mod}, false} -> 64 | # which_struct returns not expanded aliases 65 | # TODO use Macro.Env 66 | {:atom, Introspection.expand_alias(mod, aliases)} 67 | 68 | _ -> 69 | type 70 | end 71 | 72 | type = Binding.expand(binding_env, {:struct, [], type, var}) 73 | 74 | result = get_fields(buffer_metadata, type, hint, fields_so_far) 75 | {result, if(fields_so_far == [], do: :maybe_struct_update)} 76 | 77 | {:map, fields_so_far, var} -> 78 | var = Binding.expand(binding_env, var) 79 | 80 | result = get_fields(buffer_metadata, var, hint, fields_so_far) 81 | {result, if(fields_so_far == [], do: :maybe_struct_update)} 82 | 83 | _ -> 84 | {[], nil} 85 | end 86 | end 87 | 88 | defp get_fields(metadata, {:map, fields, _}, hint, fields_so_far) do 89 | expand_map_field_access(metadata, fields, hint, :map, fields_so_far) 90 | end 91 | 92 | defp get_fields(metadata, {:struct, fields, type, _}, hint, fields_so_far) do 93 | expand_map_field_access(metadata, fields, hint, {:struct, type}, fields_so_far) 94 | end 95 | 96 | defp get_fields(_, _, _hint, _fields_so_far), do: [] 97 | 98 | defp expand_map_field_access(metadata, fields, hint, type, fields_so_far) do 99 | {subtype, origin, types} = 100 | case type do 101 | {:struct, {:atom, mod}} -> 102 | types = ElixirSense.Providers.Utils.Field.get_field_types(metadata, mod, true) 103 | 104 | {:struct_field, inspect(mod), types} 105 | 106 | {:struct, nil} -> 107 | {:struct_field, nil, %{}} 108 | 109 | :map -> 110 | {:map_key, nil, %{}} 111 | end 112 | 113 | for {key, _value} when is_atom(key) <- fields, 114 | key not in fields_so_far, 115 | key_str = Atom.to_string(key), 116 | Matcher.match?(key_str, hint) do 117 | spec = 118 | case types[key] do 119 | nil -> 120 | case key do 121 | :__struct__ -> origin || "atom()" 122 | :__exception__ -> "true" 123 | _ -> nil 124 | end 125 | 126 | some -> 127 | Introspection.to_string_with_parens(some) 128 | end 129 | 130 | %{ 131 | type: :field, 132 | name: key_str, 133 | subtype: subtype, 134 | origin: origin, 135 | call?: false, 136 | type_spec: spec 137 | } 138 | end 139 | |> Enum.sort_by(& &1.name) 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/location/erl.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Location.Erl do 2 | alias ElixirSense.Core.Source 3 | require Logger 4 | 5 | @moduledoc """ 6 | A parser for Erlang (.erl) files to locate positions of types, functions, and module declarations. 7 | """ 8 | 9 | @doc """ 10 | Finds the position range of a type definition in an Erlang `.erl` file. 11 | 12 | ## Parameters 13 | 14 | - `file`: The path to the Erlang file. 15 | - `name`: The name of the type (as an atom). 16 | 17 | ## Returns 18 | 19 | - `{{line, start_column}, {line, end_column}}` if found. 20 | - `nil` if not found. 21 | """ 22 | def find_type_range(file, name) do 23 | escaped = 24 | name 25 | |> Atom.to_string() 26 | |> Regex.escape() 27 | 28 | regex = ~r/^-(typep?|opaque)\s+(?#{escaped})\b/u 29 | 30 | find_range_by_regex(file, regex) 31 | end 32 | 33 | @doc """ 34 | Finds the position range of a function definition in an Erlang `.erl` file. 35 | 36 | ## Parameters 37 | 38 | - `file`: The path to the Erlang file. 39 | - `name`: The name of the function (as an atom). 40 | 41 | ## Returns 42 | 43 | - `{{line, start_column}, {line, end_column}}` if found. 44 | - `nil` if not found. 45 | """ 46 | def find_fun_range(file, name) do 47 | escaped = 48 | name 49 | |> Atom.to_string() 50 | |> Regex.escape() 51 | 52 | regex = ~r/^(?#{escaped})\b\(/u 53 | 54 | find_range_by_regex(file, regex) 55 | end 56 | 57 | @doc """ 58 | Finds the position range of a module declaration in an Erlang `.erl` file. 59 | 60 | ## Parameters 61 | 62 | - `file`: The path to the Erlang file. 63 | - `module_name`: The name of the module (as an atom). 64 | 65 | ## Returns 66 | 67 | - `{{line, start_column}, {line, end_column}}` if found. 68 | - `nil` if not found. 69 | """ 70 | def find_module_range(file, module_name) do 71 | escaped = 72 | module_name 73 | |> Atom.to_string() 74 | |> Regex.escape() 75 | 76 | # Regex to capture the module name within -module(Name). 77 | # It allows optional whitespace around the module name. 78 | regex = ~r/^-module\(\s*(?#{escaped})\s*\)\./u 79 | 80 | find_range_by_regex(file, regex) 81 | end 82 | 83 | @doc false 84 | # Generalized function to find the range of a captured group in a file based on a regex. 85 | defp find_range_by_regex(file, regex) do 86 | case File.read(file) do 87 | {:ok, content} -> 88 | content 89 | |> Source.split_lines() 90 | # Line numbers start at 1 91 | |> Enum.with_index(1) 92 | |> Enum.find_value(fn {line, line_number} -> 93 | # Use Regex.run with :index to get match positions 94 | case Regex.run(regex, line, return: :index, capture: :all_names) do 95 | [{name_start, name_length}] -> 96 | # Columns are 1-based 97 | start_column = name_start + 1 98 | end_column = name_start + 1 + name_length 99 | 100 | # Return the range as {{line, start}, {line, end}} 101 | {{line_number, start_column}, {line_number, end_column}} 102 | 103 | _ -> 104 | nil 105 | end 106 | end) 107 | 108 | {:error, reason} -> 109 | Logger.error("Error reading file: #{inspect(reason)}") 110 | nil 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/ecto/types.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugins.Ecto.Types do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Introspection 5 | alias ElixirSense.Providers.Plugins.Util 6 | alias ElixirSense.Providers.Utils.Matcher 7 | 8 | # We'll keep these values hard-coded until Ecto provides the same information 9 | # using docs' metadata. 10 | 11 | @ecto_types [ 12 | {":string", "string UTF-8 encoded", "\"hello\"", nil}, 13 | {":boolean", "boolean", "true, false", nil}, 14 | {":integer", "integer", "1, 2, 3", nil}, 15 | {":float", "float", "1.0, 2.0, 3.0", nil}, 16 | {":decimal", "Decimal", nil, nil}, 17 | {":id", "integer", "1, 2, 3", nil}, 18 | {":date", "Date", nil, nil}, 19 | {":time", "Time", nil, nil}, 20 | {":time_usec", "Time", nil, nil}, 21 | {":naive_datetime", "NaiveDateTime", nil, nil}, 22 | {":naive_datetime_usec", "NaiveDateTime", nil, nil}, 23 | {":utc_datetime", "DateTime", nil, nil}, 24 | {":utc_datetime_usec", "DateTime", nil, nil}, 25 | {"{:array, inner_type}", "list", "[value, value, ...]", "{:array, ${1:inner_type}}"}, 26 | {":map", "map", nil, nil}, 27 | {"{:map, inner_type}", "map", nil, "{:map, ${1:inner_type}}"}, 28 | {":binary_id", "binary", "<>", nil}, 29 | {":binary", "binary", "<>", nil} 30 | ] 31 | 32 | def find_builtin_types(hint, cursor_context) do 33 | text_before = cursor_context.text_before 34 | text_after = cursor_context.text_after 35 | 36 | actual_hint = 37 | if String.ends_with?(text_before, "{" <> hint) do 38 | "{" <> hint 39 | else 40 | hint 41 | end 42 | 43 | for {name, _, _, _} = type <- @ecto_types, 44 | Matcher.match?(name, actual_hint) do 45 | buitin_type_to_suggestion(type, actual_hint, text_after) 46 | end 47 | end 48 | 49 | def find_custom_types(hint, module_store) do 50 | for module <- Map.get(module_store.by_behaviour, Ecto.Type, []), 51 | type_str = inspect(module), 52 | Util.match_module?(type_str, hint) do 53 | custom_type_to_suggestion(module, hint) 54 | end 55 | end 56 | 57 | defp buitin_type_to_suggestion({type, elixir_type, literal_syntax, snippet}, hint, text_after) do 58 | [_, hint_prefix] = Regex.run(~r/(.*?)[\w0-9\._!\?\->]*$/u, hint) 59 | 60 | insert_text = String.replace_prefix(type, hint_prefix, "") 61 | snippet = snippet && String.replace_prefix(snippet, hint_prefix, "") 62 | 63 | {insert_text, snippet} = 64 | if String.starts_with?(text_after, "}") do 65 | snippet = snippet && String.replace_suffix(snippet, "}", "") 66 | insert_text = String.replace_suffix(insert_text, "}", "") 67 | {insert_text, snippet} 68 | else 69 | {insert_text, snippet} 70 | end 71 | 72 | literal_syntax_info = 73 | if literal_syntax, do: "* **Literal syntax:** `#{literal_syntax}`", else: "" 74 | 75 | doc = """ 76 | Built-in Ecto type. 77 | 78 | * **Elixir type:** `#{elixir_type}` 79 | #{literal_syntax_info}\ 80 | """ 81 | 82 | %{ 83 | type: :generic, 84 | kind: :type_parameter, 85 | label: type, 86 | insert_text: insert_text, 87 | snippet: snippet, 88 | detail: "Ecto type", 89 | documentation: doc, 90 | priority: 0 91 | } 92 | end 93 | 94 | defp custom_type_to_suggestion(type, hint) do 95 | type_str = inspect(type) 96 | {doc, _} = Introspection.get_module_docs_summary(type) 97 | 98 | %{ 99 | type: :generic, 100 | kind: :type_parameter, 101 | label: type_str, 102 | insert_text: Util.trim_leading_for_insertion(hint, type_str), 103 | detail: "Ecto custom type", 104 | documentation: doc, 105 | priority: 1 106 | } 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/module_store.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugins.ModuleStore do 2 | @moduledoc """ 3 | Caches the module list and a list of modules keyed by the behaviour they implement. 4 | """ 5 | defstruct by_behaviour: %{}, list: [], plugins: [] 6 | 7 | @type t :: %__MODULE__{ 8 | by_behaviour: %{optional(atom) => module}, 9 | list: list(module), 10 | plugins: list(module) 11 | } 12 | 13 | alias ElixirSense.Core.Applications 14 | 15 | def ensure_compiled(context, module_or_modules) do 16 | modules = List.wrap(module_or_modules) 17 | Enum.each(modules, &Code.ensure_compiled/1) 18 | 19 | Map.update!(context, :module_store, &build(modules, &1)) 20 | end 21 | 22 | def build(list \\ all_loaded(), module_store \\ %__MODULE__{}) do 23 | Enum.reduce(list, module_store, fn module, module_store -> 24 | try do 25 | module_store = %{module_store | list: [module | module_store.list]} 26 | 27 | module_store = 28 | if is_plugin?(module) do 29 | %{module_store | plugins: [module | module_store.plugins]} 30 | else 31 | module_store 32 | end 33 | 34 | module.module_info(:attributes) 35 | |> Enum.flat_map(fn 36 | {:behaviour, behaviours} when is_list(behaviours) -> 37 | behaviours 38 | 39 | _ -> 40 | [] 41 | end) 42 | |> Enum.reduce(module_store, &add_behaviour(module, &1, &2)) 43 | rescue 44 | _ -> 45 | module_store 46 | end 47 | end) 48 | end 49 | 50 | defp is_plugin?(module) do 51 | module.module_info(:attributes) 52 | |> Enum.any?(fn 53 | {:behaviour, behaviours} when is_list(behaviours) -> 54 | ElixirSense.Plugin in behaviours or ElixirLS.LanguageServer.Plugin in behaviours or 55 | ElixirSense.Providers.Plugin in behaviours 56 | 57 | {:is_elixir_sense_plugin, value} -> 58 | true in List.wrap(value) 59 | 60 | {:is_elixir_ls_plugin, value} -> 61 | true in List.wrap(value) 62 | 63 | _ -> 64 | false 65 | end) 66 | end 67 | 68 | defp all_loaded do 69 | Applications.get_modules_from_applications() 70 | |> Enum.filter(fn module -> 71 | try do 72 | _ = Code.ensure_compiled(module) 73 | function_exported?(module, :module_info, 0) 74 | rescue 75 | _ -> 76 | false 77 | end 78 | end) 79 | end 80 | 81 | defp add_behaviour(adopter, behaviour, module_store) do 82 | new_by_behaviour = 83 | module_store.by_behaviour 84 | |> Map.put_new_lazy(behaviour, fn -> MapSet.new() end) 85 | |> Map.update!(behaviour, &MapSet.put(&1, adopter)) 86 | 87 | %{module_store | by_behaviour: new_by_behaviour} 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/option.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugins.Option do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Providers.Plugins.Util 5 | alias ElixirSense.Providers.Utils.Matcher 6 | 7 | def find(options, hint, fun) do 8 | for option <- options, match_hint?(option, hint) do 9 | to_suggestion(option, fun) 10 | end 11 | |> Enum.sort_by(& &1.label) 12 | end 13 | 14 | def to_suggestion(option, fun) do 15 | command = 16 | if option[:values] not in [nil, []] do 17 | Util.command(:trigger_suggest) 18 | end 19 | 20 | %{ 21 | type: :generic, 22 | kind: :property, 23 | label: to_string(option.name), 24 | insert_text: "#{option.name}: ", 25 | snippet: option[:snippet], 26 | detail: "#{fun} option", 27 | documentation: option[:doc], 28 | command: command 29 | } 30 | end 31 | 32 | def match_hint?(option, hint) do 33 | option 34 | |> Map.fetch!(:name) 35 | |> to_string() 36 | |> Matcher.match?(hint) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/phoenix.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugins.Phoenix do 2 | @moduledoc false 3 | 4 | @behaviour ElixirSense.Providers.Plugin 5 | 6 | use ElixirSense.Providers.Completion.GenericReducer 7 | 8 | alias ElixirSense.Core.Source 9 | alias ElixirSense.Core.Binding 10 | alias ElixirSense.Core.Introspection 11 | alias ElixirSense.Providers.Plugins.ModuleStore 12 | alias ElixirSense.Providers.Plugins.Phoenix.Scope 13 | alias ElixirSense.Providers.Plugins.Util 14 | alias ElixirSense.Providers.Utils.Matcher 15 | 16 | @phoenix_route_funcs ~w( 17 | get put patch trace 18 | delete head options 19 | forward connect post 20 | )a 21 | 22 | @impl true 23 | def setup(context) do 24 | ModuleStore.ensure_compiled(context, Phoenix.Router) 25 | end 26 | 27 | if Version.match?(System.version(), ">= 1.14.0-dev") do 28 | @impl true 29 | def suggestions(hint, {Phoenix.Router, func, 1, _info}, _list, opts) 30 | when func in @phoenix_route_funcs do 31 | binding = 32 | Binding.from_env(opts.env, opts.buffer_metadata, opts.cursor_context.cursor_position) 33 | 34 | {_, scope_alias} = Scope.within_scope(opts.cursor_context.text_before, binding) 35 | 36 | case find_controllers(opts.module_store, opts.env, hint, scope_alias) do 37 | [] -> :ignore 38 | controllers -> {:override, controllers} 39 | end 40 | end 41 | 42 | def suggestions( 43 | hint, 44 | {Phoenix.Router, func, 2, %{params: [_path, module]}}, 45 | _list, 46 | opts 47 | ) 48 | when func in @phoenix_route_funcs do 49 | binding_env = 50 | Binding.from_env(opts.env, opts.buffer_metadata, opts.cursor_context.cursor_position) 51 | 52 | {_, scope_alias} = Scope.within_scope(opts.cursor_context.text_before) 53 | {module, _} = Source.get_mod([module], binding_env) 54 | 55 | module = Module.concat(scope_alias, module) 56 | 57 | suggestions = 58 | for {export, {2, :function}} when export not in ~w(action call)a <- 59 | Introspection.get_exports(module), 60 | name = inspect(export), 61 | Matcher.match?(name, hint) do 62 | %{ 63 | type: :generic, 64 | kind: :function, 65 | label: name, 66 | insert_text: Util.trim_leading_for_insertion(hint, name), 67 | detail: "Phoenix action" 68 | } 69 | end 70 | 71 | {:override, suggestions} 72 | end 73 | end 74 | 75 | @impl true 76 | def suggestions(_hint, _func_call, _list, _opts) do 77 | :ignore 78 | end 79 | 80 | defp find_controllers(module_store, env, hint, scope_alias) do 81 | [prefix | _] = 82 | env.module 83 | |> inspect() 84 | |> String.split(".") 85 | 86 | for module <- module_store.list, 87 | mod_str = inspect(module), 88 | Util.match_module?(mod_str, prefix), 89 | mod_str =~ "Controller", 90 | Util.match_module?(mod_str, hint) do 91 | {doc, _} = Introspection.get_module_docs_summary(module) 92 | 93 | %{ 94 | type: :generic, 95 | kind: :class, 96 | label: mod_str, 97 | insert_text: skip_scope_alias(scope_alias, mod_str), 98 | detail: "Phoenix controller", 99 | documentation: doc 100 | } 101 | end 102 | |> Enum.sort_by(& &1.label) 103 | end 104 | 105 | defp skip_scope_alias(nil, insert_text), do: insert_text 106 | 107 | defp skip_scope_alias(scope_alias, insert_text), 108 | do: String.replace_prefix(insert_text, "#{inspect(scope_alias)}.", "") 109 | end 110 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/phoenix/scope.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugins.Phoenix.Scope do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Source 5 | alias ElixirSense.Core.Binding 6 | 7 | def within_scope(buffer, binding_env \\ %Binding{}) do 8 | with {:ok, ast} <- Code.Fragment.container_cursor_to_quoted(buffer), 9 | {true, scopes_ast} <- get_scopes(ast), 10 | scopes_ast = Enum.reverse(scopes_ast), 11 | scope_alias <- get_scope_alias(scopes_ast, binding_env) do 12 | {true, scope_alias} 13 | else 14 | _ -> {false, nil} 15 | end 16 | end 17 | 18 | defp get_scopes(ast) do 19 | path = Macro.path(ast, &match?({:__cursor__, _, list} when is_list(list), &1)) 20 | 21 | scopes = 22 | path 23 | |> Enum.filter(&match?({:scope, _, _}, &1)) 24 | |> Enum.map(fn {:scope, meta, params} -> 25 | params = Enum.reject(params, &match?([{:do, _} | _], &1)) 26 | {:scope, meta, params} 27 | end) 28 | 29 | case scopes do 30 | [] -> {false, nil} 31 | scopes -> {true, scopes} 32 | end 33 | end 34 | 35 | # scope path: "/", alias: ExampleWeb do ... end 36 | defp get_scope_alias_from_ast_node({:scope, _, [scope_params]}, binding_env, module) 37 | when is_list(scope_params) do 38 | scope_alias = Keyword.get(scope_params, :alias) 39 | concat_module(scope_alias, binding_env, module) 40 | end 41 | 42 | # scope "/", alias: ExampleWeb do ... end 43 | defp get_scope_alias_from_ast_node( 44 | {:scope, _, [_scope_path, scope_params]}, 45 | binding_env, 46 | module 47 | ) 48 | when is_list(scope_params) do 49 | scope_alias = Keyword.get(scope_params, :alias) 50 | concat_module(scope_alias, binding_env, module) 51 | end 52 | 53 | defp get_scope_alias_from_ast_node( 54 | {:scope, _, [_scope_path, scope_alias]}, 55 | binding_env, 56 | module 57 | ) do 58 | concat_module(scope_alias, binding_env, module) 59 | end 60 | 61 | # scope "/", ExampleWeb, host: "api." do ... end 62 | defp get_scope_alias_from_ast_node( 63 | {:scope, _, [_scope_path, scope_alias, scope_params]}, 64 | binding_env, 65 | module 66 | ) 67 | when is_list(scope_params) do 68 | concat_module(scope_alias, binding_env, module) 69 | end 70 | 71 | defp get_scope_alias_from_ast_node( 72 | _ast, 73 | _binding_env, 74 | module 75 | ), 76 | do: module 77 | 78 | # no alias - propagate parent 79 | defp concat_module(nil, _binding_env, module), do: module 80 | # alias: false resets all nested aliases 81 | defp concat_module(false, _binding_env, _module), do: nil 82 | 83 | defp concat_module(scope_alias, binding_env, module) do 84 | scope_alias = get_mod(scope_alias, binding_env) 85 | Module.concat([module, scope_alias]) 86 | end 87 | 88 | defp get_scope_alias(scopes_ast, binding_env, module \\ nil) 89 | # recurse 90 | defp get_scope_alias([], _binding_env, module), do: module 91 | 92 | defp get_scope_alias([head | tail], binding_env, module) do 93 | scope_alias = get_scope_alias_from_ast_node(head, binding_env, module) 94 | get_scope_alias(tail, binding_env, scope_alias) 95 | end 96 | 97 | defp get_mod({:__aliases__, _, [scope_alias]}, binding_env) do 98 | get_mod(scope_alias, binding_env) 99 | end 100 | 101 | defp get_mod({name, meta, context}, binding_env) 102 | when is_atom(name) and is_atom(context) and 103 | name not in [:__MODULE__, :__DIR__, :__ENV__, :__CALLER__, :__STACKTRACE__, :_] do 104 | case Binding.expand(binding_env, {:variable, name, Keyword.get(meta, :version, :any)}) do 105 | {:atom, atom} -> 106 | atom 107 | 108 | _ -> 109 | nil 110 | end 111 | end 112 | 113 | defp get_mod(scope_alias, binding_env) do 114 | with {mod, _} <- Source.get_mod([scope_alias], binding_env) do 115 | mod 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/plugin.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugin do 2 | alias ElixirSense.Core.Metadata 3 | alias ElixirSense.Core.State 4 | @type suggestion :: ElixirSense.Providers.Completion.Suggestion.generic() 5 | 6 | @type context :: term 7 | @type acc :: %{context: context(), result: list(suggestion())} 8 | @type cursor_context :: %{ 9 | text_before: String.t(), 10 | text_after: String.t(), 11 | at_module_body?: boolean 12 | } 13 | 14 | @callback reduce( 15 | hint :: String, 16 | env :: State.Env.t(), 17 | buffer_metadata :: Metadata.t(), 18 | cursor_context, 19 | acc 20 | ) :: {:cont, acc} | {:halt, acc} 21 | 22 | @callback setup(context()) :: context() 23 | 24 | @callback decorate(suggestion) :: suggestion 25 | 26 | @optional_callbacks decorate: 1, reduce: 5, setup: 1 27 | end 28 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/plugins/util.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Plugins.Util do 2 | @moduledoc false 3 | 4 | alias ElixirSense.Core.Binding 5 | alias ElixirSense.Core.Introspection 6 | alias ElixirSense.Core.Metadata 7 | alias ElixirSense.Core.Source 8 | alias ElixirSense.Core.State 9 | alias ElixirSense.Providers.Utils.Matcher 10 | 11 | def match_module?(mod_str, hint) do 12 | hint = String.downcase(hint) 13 | mod_full = String.downcase(mod_str) 14 | mod_last = mod_full |> String.split(".") |> List.last() 15 | Enum.any?([mod_last, mod_full], &Matcher.match?(&1, hint)) 16 | end 17 | 18 | def trim_leading_for_insertion(hint, value) do 19 | [_, hint_prefix] = Regex.run(~r/(.*?)[\w0-9\._!\?\->]*$/u, hint) 20 | insert_text = String.replace_prefix(value, hint_prefix, "") 21 | 22 | case String.split(hint, ".") do 23 | [] -> 24 | insert_text 25 | 26 | hint_parts -> 27 | parts = String.split(insert_text, ".") 28 | {_, insert_parts} = Enum.split(parts, length(hint_parts) - 1) 29 | Enum.join(insert_parts, ".") 30 | end 31 | end 32 | 33 | # TODO this is vscode specific. Remove? 34 | def command(:trigger_suggest) do 35 | %{ 36 | "title" => "Trigger Parameter Hint", 37 | "command" => "editor.action.triggerSuggest" 38 | } 39 | end 40 | 41 | def actual_mod_fun({mod, fun}, elixir_prefix, env, buffer_metadata, cursor_position) do 42 | %Metadata{mods_funs_to_positions: mods_funs, types: metadata_types} = buffer_metadata 43 | 44 | Introspection.actual_mod_fun( 45 | {mod, fun}, 46 | env, 47 | mods_funs, 48 | metadata_types, 49 | cursor_position, 50 | not elixir_prefix 51 | ) 52 | end 53 | 54 | def partial_func_call(code, %State.Env{} = env, %Metadata{} = buffer_metadata, cursor_position) do 55 | binding_env = Binding.from_env(env, buffer_metadata, cursor_position) 56 | 57 | func_info = Source.which_func(code, binding_env) 58 | 59 | with %{candidate: {mod, fun}, npar: npar} <- func_info, 60 | mod_fun <- 61 | actual_mod_fun( 62 | {mod, fun}, 63 | func_info.elixir_prefix, 64 | env, 65 | buffer_metadata, 66 | cursor_position 67 | ), 68 | {actual_mod, actual_fun, _, _} <- mod_fun do 69 | {actual_mod, actual_fun, npar, func_info} 70 | else 71 | _ -> 72 | :none 73 | end 74 | end 75 | 76 | def func_call_chain(code, env, buffer_metadata, cursor_position) do 77 | func_call_chain(code, env, buffer_metadata, cursor_position, []) 78 | end 79 | 80 | # TODO reimplement this on elixir 1.14 with 81 | # Code.Fragment.container_cursor_to_quoted and Macro.path 82 | defp func_call_chain(code, env, buffer_metadata, cursor_position, chain) do 83 | case partial_func_call(code, env, buffer_metadata, cursor_position) do 84 | :none -> 85 | Enum.reverse(chain) 86 | 87 | {_mod, _fun, _npar, %{pos: {{line, col}, _}}} = func_call -> 88 | code_before = Source.text_before(code, line, col) 89 | func_call_chain(code_before, env, buffer_metadata, cursor_position, [func_call | chain]) 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/utils/field.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Utils.Field do 2 | alias ElixirSense.Core.Metadata 3 | alias ElixirSense.Core.State 4 | alias ElixirSense.Core.Normalized.Typespec 5 | alias ElixirSense.Core.TypeInfo 6 | 7 | def get_field_types(%Metadata{} = metadata, mod, include_private) when is_atom(mod) do 8 | case get_field_types_from_metadata(metadata, mod, include_private) do 9 | nil -> get_field_types_from_introspection(mod, include_private) 10 | res -> res 11 | end 12 | end 13 | 14 | defguardp type_is_public(kind, include_private) when kind == :type or include_private 15 | 16 | defp get_field_types_from_metadata( 17 | %Metadata{types: types}, 18 | mod, 19 | include_private 20 | ) do 21 | case types[{mod, :t, 0}] do 22 | %State.TypeInfo{specs: [type_spec], kind: kind} 23 | when type_is_public(kind, include_private) -> 24 | case Code.string_to_quoted(type_spec, emit_warnings: false) do 25 | {:ok, {:@, _, [{_kind, _, [spec]}]}} -> 26 | spec 27 | |> get_fields_from_struct_spec() 28 | 29 | _ -> 30 | nil 31 | end 32 | 33 | _ -> 34 | nil 35 | end 36 | end 37 | 38 | defp get_field_types_from_introspection(nil, _include_private), do: %{} 39 | 40 | defp get_field_types_from_introspection(mod, include_private) when is_atom(mod) do 41 | # assume struct typespec is t() 42 | case TypeInfo.get_type_spec(mod, :t, 0) do 43 | {kind, spec} when type_is_public(kind, include_private) -> 44 | spec 45 | |> Typespec.type_to_quoted() 46 | |> get_fields_from_struct_spec() 47 | 48 | _ -> 49 | %{} 50 | end 51 | end 52 | 53 | defp get_fields_from_struct_spec({:"::", _, [_, {:%, _meta1, [_mod, {:%{}, _meta2, fields}]}]}) do 54 | if Keyword.keyword?(fields) do 55 | Map.new(fields) 56 | else 57 | %{} 58 | end 59 | end 60 | 61 | defp get_fields_from_struct_spec(_), do: %{} 62 | end 63 | -------------------------------------------------------------------------------- /lib/elixir_sense/providers/utils/matcher.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.Utils.Matcher do 2 | @moduledoc """ 3 | ## Suggestion Matching 4 | """ 5 | 6 | import Kernel, except: [match?: 2] 7 | 8 | @doc """ 9 | Naive sequential fuzzy matching without weight and requiring first char to match. 10 | 11 | ## Examples 12 | 13 | iex> ElixirLS.Utils.Matcher.match?("map", "map") 14 | true 15 | 16 | iex> ElixirLS.Utils.Matcher.match?("map", "m") 17 | true 18 | 19 | iex> ElixirLS.Utils.Matcher.match?("map", "ma") 20 | true 21 | 22 | iex> ElixirLS.Utils.Matcher.match?("map", "mp") 23 | true 24 | 25 | iex> ElixirLS.Utils.Matcher.match?("map", "") 26 | true 27 | 28 | iex> ElixirLS.Utils.Matcher.match?("map", "ap") 29 | false 30 | 31 | iex> ElixirLS.Utils.Matcher.match?("", "") 32 | true 33 | 34 | iex> ElixirLS.Utils.Matcher.match?("chunk_by", "chub") 35 | true 36 | 37 | iex> ElixirLS.Utils.Matcher.match?("chunk_by", "chug") 38 | false 39 | """ 40 | @spec match?(name :: String.t(), hint :: String.t()) :: boolean() 41 | def match?(<>, <>) 42 | when name_head != hint_head do 43 | false 44 | end 45 | 46 | def match?(name, hint) do 47 | do_match?(name, hint) 48 | end 49 | 50 | defp do_match?(<>, <>) do 51 | do_match?(name_rest, hint_rest) 52 | end 53 | 54 | defp do_match?( 55 | <<_head::utf8, name_rest::binary>>, 56 | <<_not_head::utf8, _hint_rest::binary>> = hint 57 | ) do 58 | do_match?(name_rest, hint) 59 | end 60 | 61 | defp do_match?(_name_rest, <<>>) do 62 | true 63 | end 64 | 65 | defp do_match?(<<>>, <<>>) do 66 | true 67 | end 68 | 69 | defp do_match?(<<>>, _) do 70 | false 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.MixProject do 2 | @moduledoc false 3 | use Mix.Project 4 | 5 | def project do 6 | [ 7 | app: :elixir_sense, 8 | version: "2.0.0", 9 | elixir: "~> 1.13", 10 | elixirc_paths: elixirc_paths(Mix.env()), 11 | build_embedded: Mix.env() == :prod, 12 | start_permanent: Mix.env() == :prod, 13 | prune_code_paths: Mix.env() == :prod, 14 | compilers: [:yecc] ++ Mix.compilers(), 15 | test_coverage: [tool: ExCoveralls], 16 | preferred_cli_env: [coveralls: :test, "coveralls.detail": :test, "coveralls.html": :test], 17 | dialyzer: [ 18 | flags: [ 19 | :unmatched_returns, 20 | :error_handling, 21 | :unknown, 22 | :underspecs, 23 | :extra_return, 24 | :missing_return 25 | ] 26 | ], 27 | deps: deps(), 28 | docs: docs(), 29 | description: description(), 30 | package: package() 31 | ] 32 | end 33 | 34 | def application do 35 | [extra_applications: [:logger]] 36 | end 37 | 38 | defp elixirc_paths(:test), do: ["lib", "test/support"] 39 | defp elixirc_paths(_), do: ["lib"] 40 | 41 | defp deps do 42 | [ 43 | {:excoveralls, "~> 0.17", only: :test}, 44 | {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, 45 | # TODO: Uncomment this when we have a credo version that supports OTP 28 46 | # {:credo, "~> 1.0", only: [:dev], runtime: false}, 47 | {:ex_doc, "~> 0.18", only: [:dev], runtime: false} 48 | ] 49 | end 50 | 51 | defp docs do 52 | [ 53 | main: "ElixirSense", 54 | nest_modules_by_prefix: [ElixirSense.Core, ElixirSense.Providers] 55 | ] 56 | end 57 | 58 | defp description do 59 | """ 60 | An API for Elixir projects that provides context-aware information 61 | for code completion, documentation, go/jump to definition, signature info 62 | and more. 63 | """ 64 | end 65 | 66 | defp package do 67 | [ 68 | maintainers: [ 69 | "Marlus Saraiva (@msaraiva)", 70 | "Łukasz Samson (@lukaszsamson)", 71 | "Jason Axelson (@axelson)" 72 | ], 73 | licenses: ["MIT"], 74 | links: %{"GitHub" => "https://github.com/elixir-lsp/elixir_sense"} 75 | ] 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, 3 | "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, 4 | "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, 5 | "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, 6 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, 7 | "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, 8 | "excoveralls": {:hex, :excoveralls, "0.17.0", "279f124dba347903bb654bc40745c493ae265d45040001b4899ea1edf88078c7", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "08b638d114387a888f9cb8d65f2a0021ec04c3e447b793efa7c1e734aba93004"}, 9 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, 10 | "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, 11 | "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, 12 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, 13 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 14 | "nimble_parsec": {:hex, :nimble_parsec, "1.3.0", "9e18a119d9efc3370a3ef2a937bf0b24c088d9c4bf0ba9d7c3751d49d347d035", [:mix], [], "hexpm", "7977f183127a7cbe9346981e2f480dc04c55ffddaef746bd58debd566070eef8"}, 15 | } 16 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | elixir_sense_parser.erl 2 | -------------------------------------------------------------------------------- /src/elixir_sense.hrl: -------------------------------------------------------------------------------- 1 | % This file includes modified code extracted from the elixir project. Namely: 2 | % 3 | % https://github.com/elixir-lang/elixir/blob/v1.13.4/lib/elixir/src/elixir.hrl 4 | % 5 | % The original code is licensed as follows: 6 | % 7 | % Copyright 2012 Plataformatec 8 | % Copyright 2021 The Elixir Team 9 | % 10 | % Licensed under the Apache License, Version 2.0 (the "License"); 11 | % you may not use this file except in compliance with the License. 12 | % You may obtain a copy of the License at 13 | % 14 | % https://www.apache.org/licenses/LICENSE-2.0 15 | % 16 | % Unless required by applicable law or agreed to in writing, software 17 | % distributed under the License is distributed on an "AS IS" BASIS, 18 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | % See the License for the specific language governing permissions and 20 | % limitations under the License. 21 | 22 | % The only changes here are module renames 23 | 24 | -define(key(M, K), maps:get(K, M)). 25 | -define(ann(Meta), elixir_erl:get_ann(Meta)). 26 | -define(line(Meta), elixir_utils:get_line(Meta)). 27 | -define(generated(Meta), [{generated, true} | Meta]). 28 | -define(var_context, ?MODULE). 29 | -define(remote(Ann, Module, Function, Args), {call, Ann, {remote, Ann, {atom, Ann, Module}, {atom, Ann, Function}}, Args}). 30 | 31 | -record(elixir_ex, { 32 | caller=false, %% stores if __CALLER__ is allowed 33 | prematch=warn, %% {Read, Counter} | warn | raise | pin 34 | stacktrace=false, %% stores if __STACKTRACE__ is allowed 35 | unused={#{}, 0}, %% a map of unused vars and a version counter for vars 36 | vars={#{}, false} %% a tuple with maps of read and optional write current vars 37 | }). 38 | 39 | -record(elixir_erl, { 40 | context=nil, %% can be match, guards or nil 41 | extra=nil, %% extra information about the context, like pin_guard and map_key 42 | caller=false, %% when true, it means caller was invoked 43 | var_names=#{}, %% maps of defined variables and their alias 44 | extra_guards=[], %% extra guards from args expansion 45 | counter=#{}, %% a map counting the variables defined 46 | expand_captures=false, %% a boolean to control if captures should be expanded 47 | stacktrace=nil %% holds information about the stacktrace variable 48 | }). 49 | 50 | -record(elixir_tokenizer, { 51 | file=(<<"nofile">>), 52 | terminators=[], 53 | unescape=true, 54 | cursor_completion=false, 55 | existing_atoms_only=false, 56 | static_atoms_encoder=nil, 57 | preserve_comments=nil, 58 | identifier_tokenizer=elixir_tokenizer, 59 | indentation=0, 60 | mismatch_hints=[], 61 | warn_on_unnecessary_quotes=true, 62 | warnings=[] 63 | }). 64 | -------------------------------------------------------------------------------- /src/elixir_sense_config.erl: -------------------------------------------------------------------------------- 1 | % This file includes modified code extracted from the elixir project. Namely: 2 | % 3 | % https://github.com/elixir-lang/elixir/blob/v1.13.4/lib/elixir/src/elixir_config.erl 4 | % 5 | % The original code is licensed as follows: 6 | % 7 | % Copyright 2012 Plataformatec 8 | % Copyright 2021 The Elixir Team 9 | % 10 | % Licensed under the Apache License, Version 2.0 (the "License"); 11 | % you may not use this file except in compliance with the License. 12 | % You may obtain a copy of the License at 13 | % 14 | % https://www.apache.org/licenses/LICENSE-2.0 15 | % 16 | % Unless required by applicable law or agreed to in writing, software 17 | % distributed under the License is distributed on an "AS IS" BASIS, 18 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | % See the License for the specific language governing permissions and 20 | % limitations under the License. 21 | 22 | % The only changes here are module renames 23 | 24 | -module(elixir_sense_config). 25 | -compile({no_auto_import, [get/1]}). 26 | -export([new/1, warn/2, serial/1]). 27 | -export([static/1, is_bootstrap/0, identifier_tokenizer/0]). 28 | -export([delete/1, put/2, get/1, get/2, update/2, get_and_put/2]). 29 | -export([start_link/0, init/1, handle_call/3, handle_cast/2]). 30 | -behaviour(gen_server). 31 | 32 | static(Map) when is_map(Map) -> 33 | persistent_term:put(?MODULE, maps:merge(persistent_term:get(?MODULE, #{}), Map)). 34 | is_bootstrap() -> 35 | maps:get(bootstrap, persistent_term:get(?MODULE, #{}), false). 36 | identifier_tokenizer() -> 37 | maps:get(identifier_tokenizer, persistent_term:get(?MODULE, #{}), 'Elixir.String.Tokenizer'). 38 | 39 | get(Key) -> 40 | [{_, Value}] = ets:lookup(?MODULE, Key), 41 | Value. 42 | 43 | get(Key, Default) -> 44 | try ets:lookup(?MODULE, Key) of 45 | [{_, Value}] -> Value; 46 | [] -> Default 47 | catch 48 | _:_ -> Default 49 | end. 50 | 51 | put(Key, Value) -> 52 | gen_server:call(?MODULE, {put, Key, Value}). 53 | 54 | get_and_put(Key, Value) -> 55 | gen_server:call(?MODULE, {get_and_put, Key, Value}). 56 | 57 | update(Key, Fun) -> 58 | gen_server:call(?MODULE, {update, Key, Fun}). 59 | 60 | serial(Fun) -> 61 | gen_server:call(?MODULE, {serial, Fun}). 62 | 63 | %% Used to guarantee warnings are emitted only once per caller. 64 | warn(Key, [{Mod, Fun, ArgsOrArity, _} | _]) -> 65 | EtsKey = {warn, Key, Mod, Fun, to_arity(ArgsOrArity)}, 66 | ets:update_counter(?MODULE, EtsKey, {2, 1, 1, 1}, {EtsKey, -1}) =:= 0; 67 | 68 | warn(_, _) -> 69 | true. 70 | 71 | to_arity(Args) when is_list(Args) -> length(Args); 72 | to_arity(Arity) -> Arity. 73 | 74 | %% ets life-cycle api 75 | 76 | new(Opts) -> 77 | Tab = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]), 78 | true = ets:insert_new(?MODULE, Opts), 79 | Tab. 80 | 81 | delete(?MODULE) -> 82 | ets:delete(?MODULE). 83 | 84 | %% gen_server api 85 | 86 | start_link() -> 87 | gen_server:start_link({local, ?MODULE}, ?MODULE, ?MODULE, []). 88 | 89 | init(Tab) -> 90 | {ok, Tab}. 91 | 92 | handle_call({serial, Fun}, _From, Tab) -> 93 | {reply, Fun(), Tab}; 94 | handle_call({put, Key, Value}, _From, Tab) -> 95 | ets:insert(Tab, {Key, Value}), 96 | {reply, ok, Tab}; 97 | handle_call({update, Key, Fun}, _From, Tab) -> 98 | Value = Fun(get(Key)), 99 | ets:insert(Tab, {Key, Value}), 100 | {reply, Value, Tab}; 101 | handle_call({get_and_put, Key, Value}, _From, Tab) -> 102 | OldValue = get(Key), 103 | ets:insert(Tab, {Key, Value}), 104 | {reply, OldValue, Tab}. 105 | 106 | handle_cast(Cast, Tab) -> 107 | {stop, {bad_cast, Cast}, Tab}. 108 | -------------------------------------------------------------------------------- /src/elixir_sense_tokenizer.hrl: -------------------------------------------------------------------------------- 1 | % This file includes modified code extracted from the elixir project. Namely: 2 | % 3 | % https://github.com/elixir-lang/elixir/blob/v1.13.4/lib/elixir/src/elixir_tokenizer.hrl 4 | % 5 | % The original code is licensed as follows: 6 | % 7 | % Copyright 2012 Plataformatec 8 | % Copyright 2021 The Elixir Team 9 | % 10 | % Licensed under the Apache License, Version 2.0 (the "License"); 11 | % you may not use this file except in compliance with the License. 12 | % You may obtain a copy of the License at 13 | % 14 | % https://www.apache.org/licenses/LICENSE-2.0 15 | % 16 | % Unless required by applicable law or agreed to in writing, software 17 | % distributed under the License is distributed on an "AS IS" BASIS, 18 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | % See the License for the specific language governing permissions and 20 | % limitations under the License. 21 | 22 | % The only changes here are module renames 23 | 24 | %% Numbers 25 | -define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))). 26 | -define(is_bin(S), (S >= $0 andalso S =< $1)). 27 | -define(is_octal(S), (S >= $0 andalso S =< $7)). 28 | 29 | %% Digits and letters 30 | -define(is_digit(S), (S >= $0 andalso S =< $9)). 31 | -define(is_upcase(S), (S >= $A andalso S =< $Z)). 32 | -define(is_downcase(S), (S >= $a andalso S =< $z)). 33 | 34 | %% Others 35 | -define(is_quote(S), (S =:= $" orelse S =:= $')). 36 | -define(is_sigil(S), (S =:= $/ orelse S =:= $< orelse S =:= $" orelse S =:= $' orelse 37 | S =:= $[ orelse S =:= $( orelse S =:= ${ orelse S =:= $|)). 38 | 39 | %% Spaces 40 | -define(is_horizontal_space(S), (S =:= $\s orelse S =:= $\t)). 41 | -define(is_vertical_space(S), (S =:= $\r orelse S =:= $\n)). 42 | -define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))). 43 | 44 | %% Bidirectional control 45 | %% Retrieved from https://trojansource.codes/trojan-source.pdf 46 | -define(bidi(C), C =:= 16#202A; 47 | C =:= 16#202B; 48 | C =:= 16#202D; 49 | C =:= 16#202E; 50 | C =:= 16#2066; 51 | C =:= 16#2067; 52 | C =:= 16#2068; 53 | C =:= 16#202C; 54 | C =:= 16#2069). 55 | -------------------------------------------------------------------------------- /test/elixir_sense/all_modules_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.ModulesTest do 2 | use ExUnit.Case, async: true 3 | 4 | test "test all modules available modules are listed" do 5 | modules = ElixirSense.all_modules() 6 | assert "ElixirSense" in modules 7 | assert ":kernel" in modules 8 | assert ":erlang" in modules 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/elixir_sense/core/builtin_functions_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.BuiltinFunctionsTest do 2 | use ExUnit.Case, async: true 3 | alias ElixirSense.Core.BuiltinFunctions 4 | 5 | test "gets specs" do 6 | assert [ 7 | "@spec module_info(:module) :: atom", 8 | "@spec module_info(:attributes | :compile) :: [{atom, term}]", 9 | "@spec module_info(:md5) :: binary", 10 | "@spec module_info(:exports | :functions | :nifs) :: [{atom, non_neg_integer}]", 11 | "@spec module_info(:native) :: boolean" 12 | ] == BuiltinFunctions.get_specs({:module_info, 1}) 13 | end 14 | 15 | test "gets args" do 16 | assert ["key"] == BuiltinFunctions.get_args({:module_info, 1}) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/elixir_sense/core/metadata_builder/alias_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.MetadataBuilder.AliasTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias ElixirSense.Core.MetadataBuilder 5 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Alias 6 | 7 | for module <- [ 8 | Alias.Empty, 9 | Alias.SimpleAlias, 10 | Alias.Alias12, 11 | Alias.AliasOfAlias, 12 | Alias.AliasExternal, 13 | Alias.AliasModuleSpecial12, 14 | Alias.AliasModuleSpecialSubmodule, 15 | Alias.AliasModuleSpecialWithAs, 16 | Alias.AliasModuleSpecial, 17 | Alias.AliasWithAsOnePart, 18 | Alias.AliasOnePart, 19 | Alias.AliasSubmoduleExternal, 20 | Alias.AliasSubmoduleExternalInherit, 21 | Alias.AliasSubmoduleExternalWithAlias, 22 | Alias.AliasSubmoduleExternalWithAliasSpecial, 23 | Alias.AliasSubmoduleNestedExternal, 24 | Alias.AliasSubmoduleNested, 25 | Alias.AliasSubmodule, 26 | Alias.AliasInSubmodule, 27 | Alias.AliasWithAsErlang, 28 | Alias.AliasWithAs, 29 | Alias.AliasWithWarn, 30 | Alias.AliasWithWarnAs, 31 | Alias.AliasInheritFunction, 32 | Alias.AliasInheritSubmodule, 33 | Alias.AliasNoLeakFunction, 34 | Alias.AliasNoLeakSubmodule, 35 | Alias.AliasNoLeakBlock, 36 | Alias.AliasNoLeakClause, 37 | Alias.RealiasInScope, 38 | Alias.Realias, 39 | Alias.Unalias, 40 | Alias.Noop, 41 | Alias.NoUnaliasNested, 42 | Alias.RequireWithAs, 43 | Alias.RequireWithWarnAs, 44 | Alias.AfterStructWithImplementation, 45 | Alias.AliasSubmoduleExternalSpecial, 46 | Alias.AliasSubmoduleExternalSpecialInside, 47 | Alias.AliasSubmoduleExternalInside 48 | ] do 49 | test "alias rules properly handled in #{inspect(module)}" do 50 | state = 51 | unquote(module).module_info()[:compile][:source] 52 | |> File.read!() 53 | |> Code.string_to_quoted(columns: true, token_metadata: true) 54 | |> MetadataBuilder.build() 55 | 56 | env = unquote(module).env() 57 | 58 | assert metadata_env = state.lines_to_env[env.line] 59 | 60 | assert metadata_env.aliases == env.aliases 61 | # assert State.macro_env(state, metadata_env, env.line) == env 62 | end 63 | end 64 | 65 | test "auto aliased" do 66 | code = """ 67 | __ENV__ 68 | """ 69 | 70 | state = 71 | code 72 | |> Code.string_to_quoted(columns: true, token_metadata: true) 73 | |> MetadataBuilder.build() 74 | 75 | {env, _} = Code.eval_string(code, []) 76 | assert metadata_env = state.lines_to_env[env.line] 77 | 78 | assert Enum.sort(metadata_env.aliases) == Enum.sort(env.aliases) 79 | end 80 | 81 | test "auto aliased after last module" do 82 | code = """ 83 | defmodule Alias.Some.B.C do 84 | end 85 | __ENV__ 86 | """ 87 | 88 | state = 89 | code 90 | |> Code.string_to_quoted(columns: true, token_metadata: true) 91 | |> MetadataBuilder.build() 92 | 93 | {env, _} = Code.eval_string(code, []) 94 | assert metadata_env = state.lines_to_env[env.line] 95 | 96 | assert Enum.sort(metadata_env.aliases) == Enum.sort(env.aliases) 97 | after 98 | :code.delete(Some.B.C) 99 | :code.purge(Some.B.C) 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /test/elixir_sense/core/metadata_builder/import_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.MetadataBuilder.ImportTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias ElixirSense.Core.MetadataBuilder 5 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Import 6 | 7 | for module <- [ 8 | Import.Empty, 9 | Import.SimpleImport, 10 | Import.ImportOnlyFunctions, 11 | Import.ImportOnlyMacros, 12 | Import.ImportOnlySigils, 13 | Import.ImportOnlyUnderscored, 14 | Import.ImportOnlyList, 15 | Import.ImportOverwrite, 16 | Import.ImportOverwriteInScope, 17 | Import.ImportOverwriteNoLeak, 18 | Import.ImportExceptList, 19 | Import.ImportExceptModify, 20 | Import.ImportErlang, 21 | Import.ImportErlangBehaviour, 22 | Import.ImportElixirBehaviour, 23 | Import.Transitive, 24 | Import.Import12, 25 | Import.ImportOfAlias, 26 | Import.ImportExternal, 27 | Import.ImportWithWarn, 28 | Import.ImportOnePart, 29 | Import.ImportInSubmodule, 30 | Import.ImportInheritFunction, 31 | Import.ImportInheritSubmodule, 32 | Import.ImportNoLeakFunction, 33 | Import.ImportNoLeakSubmodule, 34 | Import.ImportNoLeakBlock, 35 | Import.ImportNoLeakClause 36 | ] do 37 | test "import rules properly handled in #{inspect(module)}" do 38 | state = 39 | unquote(module).module_info()[:compile][:source] 40 | |> File.read!() 41 | |> Code.string_to_quoted(columns: true, token_metadata: true) 42 | |> MetadataBuilder.build() 43 | 44 | env = unquote(module).env() 45 | 46 | assert metadata_env = state.lines_to_env[env.line] 47 | 48 | assert deep_sort(metadata_env.functions) == deep_sort(env.functions) 49 | assert deep_sort(metadata_env.macros) == deep_sort(env.macros) 50 | end 51 | end 52 | 53 | defp deep_sort(keyword) do 54 | keyword 55 | |> Enum.map(fn {k, v} -> {k, Enum.sort(v)} end) 56 | |> Enum.sort_by(fn {k, _} -> k end) 57 | end 58 | 59 | test "auto imported" do 60 | code = """ 61 | __ENV__ 62 | """ 63 | 64 | state = 65 | code 66 | |> Code.string_to_quoted(columns: true, token_metadata: true) 67 | |> MetadataBuilder.build() 68 | 69 | {env, _} = Code.eval_string(code, []) 70 | assert metadata_env = state.lines_to_env[env.line] 71 | 72 | assert deep_sort(metadata_env.functions) == deep_sort(env.functions) 73 | assert deep_sort(metadata_env.macros) == deep_sort(env.macros) 74 | end 75 | 76 | test "auto imported after last module" do 77 | code = """ 78 | defmodule Import.Some.B.C do 79 | end 80 | __ENV__ 81 | """ 82 | 83 | state = 84 | code 85 | |> Code.string_to_quoted(columns: true, token_metadata: true) 86 | |> MetadataBuilder.build() 87 | 88 | {env, _} = Code.eval_string(code, []) 89 | assert metadata_env = state.lines_to_env[env.line] 90 | 91 | assert deep_sort(metadata_env.functions) == deep_sort(env.functions) 92 | assert deep_sort(metadata_env.macros) == deep_sort(env.macros) 93 | after 94 | :code.delete(Some.B.C) 95 | :code.purge(Some.B.C) 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /test/elixir_sense/core/metadata_builder/require_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.MetadataBuilder.RequireTest do 2 | use ExUnit.Case, async: true 3 | 4 | alias ElixirSense.Core.MetadataBuilder 5 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Require 6 | 7 | for module <- [ 8 | Require.Empty, 9 | Require.Require12, 10 | Require.RequireOfAlias, 11 | Require.RequireExternal, 12 | Require.SimpleRequire, 13 | Require.RequireInSubmodule, 14 | Require.RequireWithAs, 15 | Require.RequireWithWarn, 16 | Require.RequireWithAsOnePart, 17 | Require.RequireSubmodule, 18 | Require.RequireNoLeakFunction, 19 | Require.RequireNoLeakSubmodule, 20 | Require.RequireInheritFunction, 21 | Require.RequireInheritSubmodule, 22 | Require.RequireNoLeakBlock, 23 | Require.RequireNoLeakClause, 24 | Require.RequireOnePart, 25 | Require.Import, 26 | Require.Use 27 | ] do 28 | test "require rules properly handled in #{inspect(module)}" do 29 | state = 30 | unquote(module).module_info()[:compile][:source] 31 | |> File.read!() 32 | |> Code.string_to_quoted(columns: true, token_metadata: true) 33 | |> MetadataBuilder.build() 34 | 35 | env = unquote(module).env() 36 | 37 | assert metadata_env = state.lines_to_env[env.line] 38 | 39 | assert Enum.sort(metadata_env.requires) == Enum.sort(env.requires) 40 | end 41 | end 42 | 43 | test "auto required" do 44 | code = """ 45 | __ENV__ 46 | """ 47 | 48 | state = 49 | code 50 | |> Code.string_to_quoted(columns: true, token_metadata: true) 51 | |> MetadataBuilder.build() 52 | 53 | {env, _} = Code.eval_string(code, []) 54 | assert metadata_env = state.lines_to_env[env.line] 55 | 56 | assert Enum.sort(metadata_env.requires) == Enum.sort(env.requires) 57 | end 58 | 59 | test "auto required after last module" do 60 | code = """ 61 | defmodule Require.Some.B.C do 62 | end 63 | __ENV__ 64 | """ 65 | 66 | state = 67 | code 68 | |> Code.string_to_quoted(columns: true, token_metadata: true) 69 | |> MetadataBuilder.build() 70 | 71 | {env, _} = Code.eval_string(code, []) 72 | assert metadata_env = state.lines_to_env[env.line] 73 | 74 | assert Enum.sort(metadata_env.requires) == Enum.sort(env.requires) 75 | after 76 | :code.delete(Other.B.C) 77 | :code.purge(Other.B.C) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /test/elixir_sense/core/normalized/tokenizer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Normalized.TokenizerTest do 2 | use ExUnit.Case, async: true 3 | alias ElixirSense.Core.Normalized.Tokenizer 4 | 5 | test "tokenizes valid elixir source" do 6 | buffer = """ 7 | defmodule Abc do 8 | def fun(a), do: :ok 9 | end 10 | """ 11 | 12 | assert [ 13 | {:eol, {3, 4, 1}}, 14 | {:end, {3, 1, nil}}, 15 | {:eol, {2, 24, 1}}, 16 | {:atom, {2, 21, _}, :ok}, 17 | {:kw_identifier, {2, 17, _}, :do}, 18 | {:",", {2, 15, 0}}, 19 | {:")", {2, 14, nil}}, 20 | {:identifier, {2, 13, _}, :a}, 21 | {:"(", {2, 12, nil}}, 22 | {:paren_identifier, {2, 9, _}, :fun}, 23 | {:identifier, {2, 5, _}, :def}, 24 | {:eol, {1, 17, 1}}, 25 | {:do, {1, 15, nil}}, 26 | {:alias, {1, 11, _}, :Abc}, 27 | {:identifier, {1, 1, _}, :defmodule} 28 | ] = Tokenizer.tokenize(buffer) 29 | end 30 | 31 | test "tokenizes invalidvalid elixir source" do 32 | buffer = """ 33 | defmodule Abc do 34 | def jsndc(}.) 35 | """ 36 | 37 | assert [ 38 | {:"(", {2, 14, nil}}, 39 | {:paren_identifier, {2, 9, _}, :jsndc}, 40 | {:identifier, {2, 5, _}, :def}, 41 | {:eol, {1, 17, 1}}, 42 | {:do, {1, 15, nil}}, 43 | {:alias, {1, 11, _}, :Abc}, 44 | {:identifier, {1, 1, _}, :defmodule} 45 | ] = Tokenizer.tokenize(buffer) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/elixir_sense/core/normalized/typespec_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.Normalized.TypespecTest do 2 | use ExUnit.Case, async: true 3 | alias ElixirSense.Core.Normalized.Typespec 4 | 5 | test "get_specs" do 6 | assert [ 7 | {{:"MACRO-some_macro", 2}, 8 | [ 9 | {:type, _, :fun, 10 | [ 11 | {:type, _, :product, [{:type, _, :term, []}, {:type, _, :integer, []}]}, 12 | {:remote_type, _, [{:atom, 0, Macro}, {:atom, 0, :t}, []]} 13 | ]} 14 | ]}, 15 | {{:some_fun_priv, 1}, 16 | [ 17 | {:type, _, :fun, 18 | [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]} 19 | ]}, 20 | {{:some_fun, 1}, 21 | [ 22 | {:type, _, :fun, 23 | [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]} 24 | ]} 25 | ] = Typespec.get_specs(ElixirSenseExample.ModuleWithTypes) 26 | 27 | assert [] == Typespec.get_specs(ElixirSenseExample.NotExistingModule) 28 | end 29 | 30 | test "get_types" do 31 | assert [ 32 | typep: {:priv_type, {:type, _, :integer, []}, []}, 33 | opaque: {:opaque_type, {:user_type, _, :priv_type, []}, []}, 34 | type: {:pub_type, {:type, _, :integer, []}, []} 35 | ] = Typespec.get_types(ElixirSenseExample.ModuleWithTypes) 36 | 37 | assert [] == Typespec.get_types(ElixirSenseExample.NotExistingModule) 38 | end 39 | 40 | test "get_callbacks" do 41 | assert [ 42 | {{:"MACRO-some_macrocallback", 2}, 43 | [ 44 | {:type, _, :fun, 45 | [ 46 | {:type, _, :product, [{:type, _, :term, []}, {:type, _, :integer, []}]}, 47 | {:type, _, :atom, []} 48 | ]} 49 | ]}, 50 | {{:some_callback, 1}, 51 | [ 52 | {:type, _, :fun, 53 | [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :atom, []}]} 54 | ]} 55 | ] = Typespec.get_callbacks(ElixirSenseExample.ModuleWithTypes) 56 | 57 | assert [] == Typespec.get_callbacks(ElixirSenseExample.NotExistingModule) 58 | end 59 | 60 | test "type_to_quoted" do 61 | type = {:t, {:remote_type, 249, [{:atom, 0, Enumerable}, {:atom, 0, :t}, []]}, []} 62 | 63 | assert {:"::", [], [{:t, [], []}, {{:., [line: 249], [Enumerable, :t]}, [line: 249], []}]} == 64 | Typespec.type_to_quoted(type) 65 | end 66 | 67 | test "spec_to_quoted" do 68 | spec = 69 | {:type, 456, :fun, 70 | [ 71 | {:type, 456, :product, [{:type, 456, :term, []}]}, 72 | {:type, 456, :maybe_improper_list, []} 73 | ]} 74 | 75 | assert {:"::", [line: 456], 76 | [ 77 | {:wrap, [line: 456], [{:term, [line: 456], []}]}, 78 | {:maybe_improper_list, [line: 456], []} 79 | ]} == Typespec.spec_to_quoted(:wrap, spec) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/elixir_sense/core/type_info_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.TypeInfoTest do 2 | use ExUnit.Case, async: true 3 | alias ElixirSense.Core.TypeInfo 4 | 5 | test "builtin_type_documentation" do 6 | assert [%{name: "any", params: [], spec: "@type any"}] = 7 | TypeInfo.get_signatures(nil, :any, nil) 8 | 9 | assert [%{name: "pid", params: [], spec: "@type pid"}] = 10 | TypeInfo.get_signatures(nil, :pid, nil) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/elixir_sense/log_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.LogTest do 2 | use ExUnit.Case 3 | import ExUnit.CaptureLog 4 | import ElixirSense.Log 5 | 6 | def with_logging_disabled(_) do 7 | orig_value = Application.get_env(:elixir_sense, :logging_enabled) 8 | Application.put_env(:elixir_sense, :logging_enabled, false) 9 | on_exit(fn -> Application.put_env(:elixir_sense, :logging_enabled, orig_value) end) 10 | :ok 11 | end 12 | 13 | describe "log messages" do 14 | test "an info message has an info label by default" do 15 | message = assert capture_log(fn -> info("good morning") end) 16 | assert message =~ "[info] " 17 | assert message =~ "good morning\n" 18 | end 19 | 20 | test "an error message has an error label by default" do 21 | message = assert capture_log(fn -> error("good morning") end) 22 | assert message =~ "[error] " 23 | assert message =~ "good morning\n" 24 | end 25 | end 26 | 27 | describe "with logging disabled" do 28 | setup [:with_logging_disabled] 29 | 30 | test "info emits no output" do 31 | assert capture_log(fn -> info("hello") end) == "" 32 | end 33 | 34 | test "warn emits no output" do 35 | assert capture_log(fn -> warn("hello") end) == "" 36 | assert capture_log(fn -> warn("hello") end) == "" 37 | end 38 | 39 | test "error emits no output" do 40 | assert capture_log(fn -> error("hello") end) == "" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/misc/mock_elixir_src/lib/elixir/lib/string.ex: -------------------------------------------------------------------------------- 1 | import Kernel, except: [length: 1] 2 | 3 | defmodule String do 4 | @typedoc """ 5 | A UTF-8 encoded binary. 6 | 7 | The types `String.t()` and `binary()` are equivalent to analysis tools. 8 | Although, for those reading the documentation, `String.t()` implies 9 | it is a UTF-8 encoded binary. 10 | """ 11 | @type t :: binary 12 | 13 | @doc """ 14 | Returns the number of Unicode graphemes in a UTF-8 string. 15 | 16 | ## Examples 17 | 18 | iex> String.length("elixir") 19 | 6 20 | 21 | iex> String.length("եոգլի") 22 | 5 23 | 24 | """ 25 | @spec length(t) :: non_neg_integer 26 | def length(string) when is_binary(string), do: length(string, 0) 27 | 28 | defp length(gcs, acc) do 29 | case :unicode_util.gc(gcs) do 30 | [_ | rest] -> length(rest, acc + 1) 31 | [] -> acc 32 | {:error, <<_, rest::bits>>} -> length(rest, acc + 1) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/support/behaviour_implementations.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.DummyBehaviour do 2 | @callback foo() :: any 3 | end 4 | 5 | defmodule ElixirSenseExample.DummyBehaviourImplementation do 6 | @behaviour ElixirSenseExample.DummyBehaviour 7 | def foo(), do: :ok 8 | end 9 | -------------------------------------------------------------------------------- /test/support/behaviour_with_macrocallbacks.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.BehaviourWithMacrocallback do 2 | @doc """ 3 | A required macrocallback 4 | """ 5 | @macrocallback required(atom) :: Macro.t() 6 | 7 | @doc """ 8 | An optional macrocallback 9 | """ 10 | @macrocallback optional(a) :: Macro.t() when a: atom 11 | 12 | @optional_callbacks [optional: 1] 13 | end 14 | 15 | defmodule ElixirSenseExample.BehaviourWithMacrocallback.Impl do 16 | @behaviour ElixirSenseExample.BehaviourWithMacrocallback 17 | defmacro required(var), do: Macro.expand(var, __CALLER__) 18 | defmacro optional(var), do: Macro.expand(var, __CALLER__) 19 | 20 | @doc """ 21 | some macro 22 | """ 23 | @spec some(integer) :: Macro.t() 24 | @spec some(b) :: Macro.t() when b: float 25 | 26 | defmacro some(var), do: Macro.expand(var, __CALLER__) 27 | 28 | @doc """ 29 | some macro with default arg 30 | """ 31 | @spec with_default(atom, list, integer) :: Macro.t() 32 | defmacro with_default(a \\ :asdf, b, var \\ 0), do: Macro.expand({a, b, var}, __CALLER__) 33 | end 34 | -------------------------------------------------------------------------------- /test/support/callback_opaque.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.CallbackOpaque do 2 | @moduledoc """ 3 | Behaviour with opaque type in callback 4 | """ 5 | 6 | @typedoc """ 7 | Opaque type 8 | """ 9 | @opaque t(x) :: {term, x} 10 | 11 | @doc """ 12 | Does stuff to opaque arg 13 | """ 14 | @callback do_stuff(t(a), term) :: t(a) when a: any 15 | end 16 | -------------------------------------------------------------------------------- /test/support/case_template_example.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.CaseTemplateExample do 2 | use ExUnit.CaseTemplate 3 | 4 | using do 5 | quote do 6 | alias Some.Module 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/support/empty_module.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.EmptyModule do 2 | @moduledoc """ 3 | Empty module without other functions 4 | 5 | More moduledoc 6 | """ 7 | end 8 | -------------------------------------------------------------------------------- /test/support/example_protocol.ex: -------------------------------------------------------------------------------- 1 | defprotocol ElixirSenseExample.ExampleProtocol do 2 | @spec some(t) :: any 3 | def some(t) 4 | end 5 | 6 | defimpl ElixirSenseExample.ExampleProtocol, for: List do 7 | def some(t), do: t 8 | end 9 | 10 | defimpl ElixirSenseExample.ExampleProtocol, for: Map do 11 | def some(t), do: t 12 | end 13 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/after_strct_with_implementation.ex: -------------------------------------------------------------------------------- 1 | defprotocol ElixirSenseExample.ExampleProtocol1 do 2 | @spec some(t) :: any 3 | def some(t) 4 | end 5 | 6 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.StructWithImplementation do 7 | defstruct name_version: "", github_relative_path: "" 8 | 9 | defimpl ElixirSenseExample.ExampleProtocol1 do 10 | def some(t), do: t 11 | end 12 | end 13 | 14 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AfterStructWithImplementation do 15 | @env __ENV__ 16 | def env, do: @env 17 | end 18 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/alias_12.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.Alias12 do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.{ 3 | Aliased, 4 | AliasedSibling, 5 | Aliased.Child 6 | } 7 | 8 | @env __ENV__ 9 | def env, do: @env 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/alias_of_alias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasOfAlias do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | alias Aliased, as: Some 4 | alias Aliased.Child 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/empty.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.Empty do 2 | @env __ENV__ 3 | def env, do: @env 4 | end 5 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/external.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasExternal do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | alias Elixir.Aliased.Child 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/in_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasInSubmodule do 2 | defmodule Submodule do 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | 7 | def env, do: Submodule.env() 8 | end 9 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/inherit_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasInheritFunction do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | 4 | def some do 5 | __ENV__ 6 | end 7 | 8 | def env, do: some() 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/inherit_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasInheritSubmodule do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | 4 | defmodule Submodule do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Submodule.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/module_special.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasModuleSpecial do 2 | alias __MODULE__ 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/module_special_12.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasModuleSpecial12 do 2 | alias __MODULE__.{A, B.C} 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/module_special_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasModuleSpecialSubmodule do 2 | alias __MODULE__.Submodule 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/module_special_with_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasModuleSpecialWithAs do 2 | alias __MODULE__.Submodule, as: Some 3 | alias __MODULE__, as: Other 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/no_leak_block.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasNoLeakBlock do 2 | def some do 3 | try do 4 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 5 | rescue 6 | _ -> 7 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 8 | :ok 9 | catch 10 | _, _ -> 11 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 12 | :ok 13 | end 14 | 15 | receive do 16 | :x -> alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 17 | after 18 | 0 -> 19 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 20 | :ok 21 | end 22 | 23 | if true do 24 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 25 | else 26 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 27 | end 28 | 29 | __ENV__ 30 | end 31 | 32 | def env, do: some() 33 | end 34 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/no_leak_clause.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasNoLeakClause do 2 | def some do 3 | fn a -> 4 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 5 | end 6 | 7 | case true do 8 | a -> 9 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 10 | end 11 | 12 | cond do 13 | true -> 14 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 15 | end 16 | 17 | __ENV__ 18 | end 19 | 20 | def env, do: some() 21 | end 22 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/no_leak_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasNoLeakFunction do 2 | def some do 3 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 4 | :ok 5 | end 6 | 7 | @env __ENV__ 8 | def env, do: @env 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/no_leak_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasNoLeakSubmodule do 2 | defmodule Submodule do 3 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 4 | end 5 | 6 | @env __ENV__ 7 | def env, do: @env 8 | end 9 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/no_unalias_nested.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.NoUnaliasNested do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | alias Elixir.Aliased.Some 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/noop.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.Noop do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | alias Aliased 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/one_part.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasOnePart do 2 | alias Enum 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/one_part_with_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasWithAsOnePart do 2 | alias Enum, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/realias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.Realias do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | alias Enum, as: Aliased 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/realias_in_scope.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.RealiasInScope do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | 4 | defmodule Child do 5 | alias Enum, as: Aliased 6 | end 7 | 8 | @env __ENV__ 9 | def env, do: @env 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/require_with_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.RequireWithAs do 2 | require ElixirSenseExample.ExampleBehaviour, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/require_with_warn_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.RequireWithWarnAs do 2 | require ElixirSenseExample.ExampleBehaviour, warn: false, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/simple_alias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.SimpleAlias do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmodule do 2 | defmodule Submodule do 3 | end 4 | 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternal do 2 | defmodule Elixir.SubmoduleExternal do 3 | end 4 | 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external_inherit.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternalInherit do 2 | alias Abc, as: Cde 3 | 4 | defmodule Elixir.SubmoduleExternalInherit do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Elixir.SubmoduleExternalInherit.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external_inside.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternalInside do 2 | alias Some.Sub 3 | 4 | defmodule Elixir.SubmoduleExternalInside do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Elixir.SubmoduleExternalInside.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external_special.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternalSpecial do 2 | alias Some.Sub 3 | 4 | defmodule __MODULE__.SubmoduleExternal do 5 | end 6 | 7 | @env __ENV__ 8 | def env, do: @env 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external_special_inside.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternalSpecialInside do 2 | alias Some.Sub 3 | 4 | defmodule __MODULE__.SubmoduleExternal do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: __MODULE__.SubmoduleExternal.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external_with_alias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternalWithAlias do 2 | alias Abc, as: SubmoduleExternalWithAlias 3 | 4 | defmodule Elixir.SubmoduleExternalWithAlias do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Elixir.SubmoduleExternalWithAlias.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_external_with_alias_special.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleExternalWithAliasSpecial do 2 | defmodule Elixir.SubmoduleExternalWithAliasSpecial do 3 | alias Abc, as: SubmoduleExternalWithAlias 4 | alias __MODULE__ 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Elixir.SubmoduleExternalWithAliasSpecial.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_nested.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleNested do 2 | defmodule Submodule.Child do 3 | end 4 | 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/submodule_nested_external.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasSubmoduleNestedExternal do 2 | defmodule Elixir.SubmoduleExternal.ChildExternal do 3 | end 4 | 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/unalias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.Unalias do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | alias Elixir.Aliased 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/with_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasWithAs do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/with_as_erlang.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasWithAsErlang do 2 | alias :lists, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/with_warn.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasWithWarn do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased, warn: false 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/alias/with_warn_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Alias.AliasWithWarnAs do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased, warn: false, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/elixir_behaviour.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportElixirBehaviour do 2 | import GenServer 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/empty.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.Empty do 2 | @env __ENV__ 3 | def env, do: @env 4 | end 5 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/erlang.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportErlang do 2 | import :erlang, except: [alias: 1] 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/erlang_behaviour.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportErlangBehaviour do 2 | import :gen_server 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/except_list.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportExceptList do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, except: [public_fun: 0] 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/except_modify.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportExceptModify do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 3 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, except: [public_fun: 0] 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/external.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportExternal do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Logger 3 | import Elixir.Logger 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/import_12.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.Import12 do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.{ 3 | Imported, 4 | ImportedSibling, 5 | Imported.Child 6 | } 7 | 8 | @env __ENV__ 9 | def env, do: @env 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/import_of_alias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOfAlias do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Imported 3 | import Imported 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/in_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportInSubmodule do 2 | defmodule Submodule do 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | 7 | def env, do: Submodule.env() 8 | end 9 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/inherit_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportInheritFunction do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 3 | 4 | def some do 5 | __ENV__ 6 | end 7 | 8 | def env, do: some() 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/inherit_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportInheritSubmodule do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 3 | 4 | defmodule Submodule do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Submodule.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/no_leak_block.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportNoLeakBlock do 2 | def some do 3 | try do 4 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 5 | rescue 6 | _ -> 7 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 8 | :ok 9 | catch 10 | _, _ -> 11 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 12 | :ok 13 | end 14 | 15 | receive do 16 | :x -> import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 17 | after 18 | 0 -> 19 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 20 | :ok 21 | end 22 | 23 | if true do 24 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 25 | else 26 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 27 | end 28 | 29 | __ENV__ 30 | end 31 | 32 | def env, do: some() 33 | end 34 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/no_leak_clause.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportNoLeakClause do 2 | def some do 3 | fn a -> 4 | import ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 5 | end 6 | 7 | case true do 8 | a -> 9 | import ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 10 | end 11 | 12 | cond do 13 | true -> 14 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 15 | end 16 | 17 | __ENV__ 18 | end 19 | 20 | def env, do: some() 21 | end 22 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/no_leak_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportNoLeakFunction do 2 | def some do 3 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 4 | :ok 5 | end 6 | 7 | @env __ENV__ 8 | def env, do: @env 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/no_leak_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportNoLeakSubmodule do 2 | defmodule Submodule do 3 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 4 | end 5 | 6 | @env __ENV__ 7 | def env, do: @env 8 | end 9 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/one_part.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOnePart do 2 | import Enum 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/only_functions.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOnlyFunctions do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :functions 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/only_list.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOnlyList do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: [public_fun: 0] 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/only_macros.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOnlyMacros do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :macros 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/only_sigils.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOnlySigils do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :sigils 3 | 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/only_underscored.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOnlyUnderscored do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: [_underscored_fun: 0] 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/overwrite.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOverwrite do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :macros 3 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: [public_fun: 0] 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/overwrite_in_scope.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOverwriteInScope do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :macros 3 | 4 | defmodule Submodule do 5 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: [public_fun: 0] 6 | @env __ENV__ 7 | def env, do: @env 8 | end 9 | 10 | def env, do: Submodule.env() 11 | end 12 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/overwrite_no_leak.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportOverwriteNoLeak do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :macros 3 | 4 | defmodule Submodule do 5 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: [public_fun: 0] 6 | end 7 | 8 | @env __ENV__ 9 | def env, do: @env 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/simple_import.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.SimpleImport do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/transitive.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.Transitive do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported.Transitive 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/import/with_warn.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Import.ImportWithWarn do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, warn: false 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/modules.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Aliased do 2 | end 3 | 4 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.AliasedSibling do 5 | end 6 | 7 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Aliased.Child do 8 | end 9 | 10 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Imported do 11 | def public_fun, do: :ok 12 | def _underscored_fun, do: :ok 13 | defp private_fun, do: :ok 14 | defdelegate delegated_func(a), to: ElixirSenseExample.Delegates 15 | defmacro public_macro(), do: :ok 16 | defmacro _underscored_macro(), do: :ok 17 | defmacrop private_macro(), do: :ok 18 | defguard public_guard(a) when is_integer(a) 19 | defguard _underscored_guard(a) when is_integer(a) 20 | defguardp private_guard(a) when is_integer(a) 21 | def sigil_i(string, []), do: String.to_integer(string) 22 | defp sigil_j(string, []), do: String.to_integer(string) 23 | 24 | defmacro sigil_x(term, [?r]) do 25 | quote do 26 | unquote(term) |> String.reverse() 27 | end 28 | end 29 | 30 | defmacrop sigil_y(term, [?r]) do 31 | quote do 32 | unquote(term) |> String.reverse() 33 | end 34 | end 35 | end 36 | 37 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.ImportedSibling do 38 | def public_fun_sibling, do: :ok 39 | end 40 | 41 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Imported.Child do 42 | def public_fun_child, do: :ok 43 | end 44 | 45 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Imported.Transitive do 46 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported 47 | end 48 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/empty.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.Empty do 2 | @env __ENV__ 3 | def env, do: @env 4 | end 5 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/external.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireExternal do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Logger 3 | require Elixir.Logger 4 | @env __ENV__ 5 | def env, do: @env 6 | end 7 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/import.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.Import do 2 | import ElixirSenseExample.Fixtures.MetadataBuilder.Imported, only: :functions 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/in_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireInSubmodule do 2 | defmodule Submodule do 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | 7 | def env, do: Submodule.env() 8 | end 9 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/inherit_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireInheritFunction do 2 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | 4 | def some do 5 | __ENV__ 6 | end 7 | 8 | def env, do: some() 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/inherit_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireInheritSubmodule do 2 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | 4 | defmodule Submodule do 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | 9 | def env, do: Submodule.env() 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/no_leak_block.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireNoLeakBlock do 2 | def some do 3 | try do 4 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 5 | rescue 6 | _ -> 7 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 8 | :ok 9 | catch 10 | _, _ -> 11 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 12 | :ok 13 | end 14 | 15 | receive do 16 | :x -> require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 17 | after 18 | 0 -> 19 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 20 | :ok 21 | end 22 | 23 | if true do 24 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 25 | else 26 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 27 | end 28 | 29 | __ENV__ 30 | end 31 | 32 | def env, do: some() 33 | end 34 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/no_leak_clause.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireNoLeakClause do 2 | def some do 3 | fn a -> 4 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 5 | end 6 | 7 | case true do 8 | a -> 9 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 10 | end 11 | 12 | cond do 13 | true -> 14 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 15 | end 16 | 17 | __ENV__ 18 | end 19 | 20 | def env, do: some() 21 | end 22 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/no_leak_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireNoLeakFunction do 2 | def some do 3 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 4 | :ok 5 | end 6 | 7 | @env __ENV__ 8 | def env, do: @env 9 | end 10 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/no_leak_submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireNoLeakSubmodule do 2 | defmodule Submodule do 3 | require ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 4 | end 5 | 6 | @env __ENV__ 7 | def env, do: @env 8 | end 9 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/one_part.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireOnePart do 2 | require Enum 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/one_part_with_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireWithAsOnePart do 2 | require Enum, as: Some 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/require_12.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.Require12 do 2 | require ElixirSenseExample.Fixtures.MetadataBuilder.{ 3 | Aliased, 4 | AliasedSibling, 5 | Aliased.Child 6 | } 7 | 8 | @env __ENV__ 9 | def env, do: @env 10 | end 11 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/require_of_alias.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireOfAlias do 2 | alias ElixirSenseExample.Fixtures.MetadataBuilder.Aliased 3 | require Aliased, as: Some 4 | require Aliased.Child 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/simple_require.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.SimpleRequire do 2 | require ElixirSenseExample.ExampleBehaviour 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/submodule.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireSubmodule do 2 | defmodule Submodule do 3 | end 4 | 5 | @env __ENV__ 6 | def env, do: @env 7 | end 8 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/use.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.Use do 2 | use ElixirSenseExample.ExampleBehaviourWithException 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/with_as.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireWithAs do 2 | require ElixirSenseExample.ExampleBehaviour, as: MyModule 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/fixtures/metadata_builder/require/with_warn.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Fixtures.MetadataBuilder.Require.RequireWithWarn do 2 | require ElixirSenseExample.ExampleBehaviour, warn: false 3 | @env __ENV__ 4 | def env, do: @env 5 | end 6 | -------------------------------------------------------------------------------- /test/support/functions_with_default_args.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.FunctionsWithDefaultArgs do 2 | @doc "no params version" 3 | @spec my_func :: binary 4 | def my_func, do: "not this one" 5 | 6 | @doc "2 params version" 7 | @spec my_func(1 | 2) :: binary 8 | @spec my_func(1 | 2, binary) :: binary 9 | def my_func(a, b \\ "") 10 | def my_func(1, b), do: "1" <> b 11 | def my_func(2, b), do: "2" <> b 12 | 13 | @doc "3 params version" 14 | @spec my_func(1, 2, 3) :: :ok 15 | def my_func(1, 2, 3), do: :ok 16 | 17 | @spec my_func(2, 2, 3) :: :error 18 | def my_func(2, 2, 3), do: :error 19 | end 20 | 21 | for i <- 1..1 do 22 | defmodule :"Elixir.ElixirSenseExample.FunctionsWithDefaultArgs#{i}" do 23 | @moduledoc "example module" 24 | 25 | @doc "no params version" 26 | @spec my_func :: binary 27 | def my_func, do: "not this one" 28 | 29 | @doc "2 params version" 30 | @spec my_func(1 | 2) :: binary 31 | @spec my_func(1 | 2, binary) :: binary 32 | def my_func(a, b \\ "") 33 | def my_func(1, b), do: "1" <> b 34 | def my_func(2, b), do: "2" <> b 35 | 36 | @doc "3 params version" 37 | @spec my_func(1, 2, 3) :: :ok 38 | def my_func(1, 2, 3), do: :ok 39 | 40 | @spec my_func(2, 2, 3) :: :error 41 | def my_func(2, 2, 3), do: :error 42 | end 43 | end 44 | 45 | defmodule ElixirSenseExample.FunctionsWithDefaultArgsCaller do 46 | alias ElixirSenseExample.FunctionsWithDefaultArgs, as: F 47 | 48 | def go() do 49 | F.my_func() 50 | F.my_func(1) 51 | F.my_func(1, "a") 52 | F.my_func(1, 2, 3) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/support/functions_with_return_spec.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.FunctionsWithReturnSpec do 2 | defstruct [:abc] 3 | 4 | @type t :: %ElixirSenseExample.FunctionsWithReturnSpec{ 5 | abc: %{key: nil} 6 | } 7 | @type x :: %{required(:abc) => atom_1, optional(:cde) => atom_1} 8 | @type atom_1 :: :asd 9 | @type num :: number 10 | @type tup :: {:ok, :abc} 11 | @type int :: 44 12 | 13 | @spec f01() :: Abc.non_existing() 14 | def f01(), do: :ok 15 | 16 | @spec f02() :: atom_1 17 | def f02(), do: :ok 18 | 19 | @spec f03() :: num 20 | def f03(), do: :ok 21 | 22 | @spec f04() :: tup 23 | def f04(), do: :ok 24 | 25 | @spec f05() :: int 26 | def f05(), do: :ok 27 | 28 | @spec f1() :: t 29 | def f1(), do: :ok 30 | 31 | @spec f1x(any) :: t 32 | def f1x(_a), do: :ok 33 | 34 | @spec f2() :: x 35 | def f2(), do: :ok 36 | 37 | @spec f3() :: ElixirSenseExample.FunctionsWithReturnSpec.Remote.t() 38 | def f3(), do: :ok 39 | 40 | @spec f4() :: ElixirSenseExample.FunctionsWithReturnSpec.Remote.x() 41 | def f4(), do: :ok 42 | 43 | @spec f5() :: %ElixirSenseExample.FunctionsWithReturnSpec{} 44 | def f5(), do: :ok 45 | 46 | @spec f6() :: %{abc: atom} 47 | def f6(), do: :ok 48 | 49 | @spec f7() :: %{abc: String} 50 | @spec f7() :: nil 51 | def f7(), do: :ok 52 | 53 | @spec f71(integer) :: %{abc: atom} 54 | @spec f71(boolean) :: %{abc: atom} 55 | def f71(_x), do: :ok 56 | 57 | @spec f8() :: %{abc: atom} | nil 58 | def f8(), do: :ok 59 | 60 | @spec f9(a) :: %{abc: atom} when a: integer 61 | def f9(_a), do: :ok 62 | 63 | @spec f91() :: a when a: %{abc: atom} 64 | def f91(), do: :ok 65 | 66 | @spec f10(integer, integer, any) :: String 67 | def f10(_a \\ 0, _b \\ 0, _c), do: String 68 | 69 | @spec f11 :: {:ok, :some} | {:error, :some_error} 70 | def f11, do: {:ok, :some} 71 | 72 | @spec list1 :: [] 73 | def list1, do: [] 74 | 75 | @spec list2 :: list 76 | def list2, do: [] 77 | 78 | @spec list3 :: [...] 79 | def list3, do: [] 80 | 81 | @spec list4 :: [:ok] 82 | def list4, do: [:ok] 83 | 84 | @spec list5 :: list(:ok) 85 | def list5, do: [:ok] 86 | 87 | @spec list6 :: [:ok, ...] 88 | def list6, do: [:ok] 89 | 90 | @spec list7 :: nonempty_list(:ok) 91 | def list7, do: [:ok] 92 | 93 | @spec list8 :: maybe_improper_list(:ok, integer) 94 | def list8, do: [:ok] 95 | 96 | @spec list9 :: nonempty_improper_list(:ok, integer) 97 | def list9, do: [:ok] 98 | 99 | @spec list10 :: nonempty_maybe_improper_list(:ok, integer) 100 | def list10, do: [:ok] 101 | 102 | @spec list11 :: keyword 103 | def list11, do: [some: :ok] 104 | 105 | @spec list12 :: keyword(:ok) 106 | def list12, do: [some: :ok] 107 | 108 | @spec list13 :: [some: :ok] 109 | def list13, do: [some: :ok] 110 | 111 | @spec f_no_return :: no_return 112 | def f_no_return, do: :ok 113 | 114 | @spec f_any :: any 115 | def f_any, do: :ok 116 | 117 | @spec f_term :: term 118 | def f_term, do: :ok 119 | end 120 | 121 | defmodule ElixirSenseExample.FunctionsWithReturnSpec.Remote do 122 | defstruct [:abc] 123 | @type t :: %ElixirSenseExample.FunctionsWithReturnSpec.Remote{} 124 | @type x :: %{abc: atom} 125 | end 126 | -------------------------------------------------------------------------------- /test/support/functions_with_the_same_name.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.FunctionsWithTheSameName do 2 | @doc "all?/2 docs" 3 | def all?(enumerable, fun \\ fn x -> x end) do 4 | IO.inspect({enumerable, fun}) 5 | end 6 | 7 | @doc "concat/1 docs" 8 | def concat(enumerables) do 9 | IO.inspect(enumerables) 10 | end 11 | 12 | @doc "concat/2 docs" 13 | def concat(left, right) do 14 | IO.inspect({left, right}) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/support/macro_generated.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Macros do 2 | defmacro go do 3 | quote do 4 | @type my_type :: nil 5 | def my_fun(), do: :ok 6 | end 7 | end 8 | end 9 | 10 | defmodule ElixirSenseExample.MacroGenerated do 11 | require ElixirSenseExample.Macros 12 | 13 | ElixirSenseExample.Macros.go() 14 | end 15 | -------------------------------------------------------------------------------- /test/support/macro_hygiene.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Math do 2 | defmacro squared(x) do 3 | quote do 4 | x = unquote(x) 5 | x * x 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/support/module_with_builtin_type_shadowing.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithBuiltinTypeShadowing do 2 | @compile {:no_warn_undefined, {B.Callee, :fun, 0}} 3 | def plain_fun do 4 | B.Callee.fun() 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/support/module_with_functions.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithFunctions do 2 | def function_arity_zero do 3 | :return_value 4 | end 5 | 6 | def function_arity_one(_) do 7 | nil 8 | end 9 | 10 | defdelegate delegated_function, to: ElixirSenseExample.ModuleWithFunctions.DelegatedModule 11 | defdelegate delegated_function(a), to: ElixirSenseExample.ModuleWithFunctions.DelegatedModule 12 | defdelegate delegated_function(a, b), to: ElixirSenseExample.ModuleWithFunctions.DelegatedModule 13 | 14 | defmodule DelegatedModule do 15 | def delegated_function do 16 | nil 17 | end 18 | 19 | def delegated_function(a) do 20 | a 21 | end 22 | 23 | def delegated_function(a, b) do 24 | {a, b} 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/support/module_with_many_clauses.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithManyClauses do 2 | def sum(s \\ nil, f) 3 | def sum(a, nil), do: a 4 | 5 | def sum(a, b) do 6 | a + b 7 | end 8 | 9 | def sum({a, b}, x, y) do 10 | a + b + x + y 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/support/module_with_private_types.ex: -------------------------------------------------------------------------------- 1 | defmodule ModuleWithPrivateTypes do 2 | @opaque opaque_t :: atom 3 | @typep typep_t :: atom 4 | @type type_t :: atom 5 | 6 | @spec just_to_use_typep(typep_t) :: typep_t 7 | def just_to_use_typep(t) do 8 | t 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/support/module_with_record.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithRecord do 2 | require Record 3 | @doc "user docs" 4 | @doc since: "1.0.0" 5 | Record.defrecord(:user, name: "john", age: 25) 6 | @type user :: record(:user, name: String.t(), age: integer) 7 | end 8 | -------------------------------------------------------------------------------- /test/support/module_with_struct.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithStruct do 2 | defstruct [:field_1, field_2: 1] 3 | end 4 | 5 | defmodule ElixirSenseExample.ModuleWithTypedStruct do 6 | @type t :: %ElixirSenseExample.ModuleWithTypedStruct{ 7 | typed_field: %ElixirSenseExample.ModuleWithStruct{}, 8 | other: integer 9 | } 10 | defstruct [:typed_field, other: 1] 11 | end 12 | -------------------------------------------------------------------------------- /test/support/module_with_types.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithTypes do 2 | @type pub_type :: integer 3 | @typep priv_type :: integer 4 | @opaque opaque_type :: priv_type 5 | @callback some_callback(integer) :: atom 6 | @macrocallback some_macrocallback(integer) :: atom 7 | 8 | @spec some_fun_priv(integer) :: integer 9 | defp some_fun_priv(a), do: a + 1 10 | 11 | @spec some_fun(integer) :: integer 12 | def some_fun(a), do: some_fun_priv(a) + 1 13 | 14 | @spec some_macro_priv() :: Macro.t() 15 | defmacrop some_macro_priv(), do: :abc 16 | 17 | @spec some_macro(integer) :: Macro.t() 18 | defmacro some_macro(_a), do: some_macro_priv() 19 | end 20 | -------------------------------------------------------------------------------- /test/support/modules_with_docs.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.ModuleWithDocs do 2 | @moduledoc """ 3 | An example module 4 | """ 5 | @moduledoc since: "1.2.3" 6 | 7 | @typedoc """ 8 | An example type 9 | """ 10 | @typedoc since: "1.1.0" 11 | @type some_type :: integer 12 | @typedoc false 13 | @type some_type_doc_false :: integer 14 | @type some_type_no_doc :: integer 15 | 16 | @typedoc """ 17 | An example opaque type 18 | """ 19 | @opaque opaque_type :: integer 20 | 21 | @doc """ 22 | An example fun 23 | """ 24 | @doc since: "1.1.0" 25 | def some_fun(a, b \\ nil), do: a + b 26 | @doc false 27 | def some_fun_doc_false(a, b \\ nil), do: a + b 28 | def some_fun_no_doc(a, b \\ nil), do: a + b 29 | 30 | @doc """ 31 | An example macro 32 | """ 33 | @doc since: "1.1.0" 34 | defmacro some_macro(a, b \\ nil), do: a + b 35 | @doc false 36 | defmacro some_macro_doc_false(a, b \\ nil), do: a + b 37 | defmacro some_macro_no_doc(a, b \\ nil), do: a + b 38 | 39 | @doc """ 40 | An example callback 41 | """ 42 | @doc since: "1.1.0" 43 | @callback some_callback(integer) :: atom 44 | @doc false 45 | @callback some_callback_doc_false(integer) :: atom 46 | @callback some_callback_no_doc(integer) :: atom 47 | 48 | @doc """ 49 | An example callback 50 | """ 51 | @doc since: "1.1.0" 52 | @macrocallback some_macrocallback(integer) :: atom 53 | @doc false 54 | @macrocallback some_macrocallback_doc_false(integer) :: atom 55 | @macrocallback some_macrocallback_no_doc(integer) :: atom 56 | 57 | @doc """ 58 | An example fun 59 | """ 60 | @doc deprecated: "This function will be removed in a future release" 61 | def soft_deprecated_fun(_a), do: :ok 62 | 63 | @doc """ 64 | An example macro 65 | """ 66 | @doc deprecated: "This macro will be removed in a future release" 67 | defmacro soft_deprecated_macro(_a), do: :ok 68 | 69 | # As of elixir 1.10 hard deprecation by @deprecated attribute is only supported for macros and functions 70 | 71 | @doc """ 72 | An example fun 73 | """ 74 | @deprecated "This function will be removed in a future release" 75 | def hard_deprecated_fun(_a), do: :ok 76 | 77 | @doc """ 78 | An example macro 79 | """ 80 | @deprecated "This macro will be removed in a future release" 81 | defmacro hard_deprecated_macro(_a), do: :ok 82 | 83 | @doc """ 84 | An example callback 85 | """ 86 | @doc deprecated: "This callback will be removed in a future release" 87 | @callback soft_deprecated_callback(integer) :: atom 88 | 89 | @doc """ 90 | An example macrocallback 91 | """ 92 | @doc deprecated: "This callback will be removed in a future release" 93 | @macrocallback soft_deprecated_macrocallback(integer) :: atom 94 | 95 | @typedoc """ 96 | An example type 97 | """ 98 | @typedoc deprecated: "This type will be removed in a future release" 99 | @type soft_deprecated_type :: integer 100 | 101 | @optional_callbacks soft_deprecated_callback: 1, soft_deprecated_macrocallback: 1 102 | end 103 | 104 | defmodule ElixirSenseExample.ModuleWithDocFalse do 105 | @moduledoc false 106 | end 107 | 108 | defmodule ElixirSenseExample.ModuleWithNoDocs do 109 | end 110 | 111 | defmodule ElixirSenseExample.SoftDeprecatedModule do 112 | @moduledoc """ 113 | An example module 114 | """ 115 | @moduledoc deprecated: "This module will be removed in a future release" 116 | end 117 | 118 | defmodule ElixirSenseExample.ModuleWithDelegates do 119 | @doc """ 120 | A delegated function 121 | """ 122 | defdelegate delegated_fun(a, b), to: ElixirSenseExample.ModuleWithDocs, as: :some_fun_no_doc 123 | end 124 | -------------------------------------------------------------------------------- /test/support/modules_with_references.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Providers.ReferencesTest.Modules do 2 | defmodule Callee1 do 3 | def func() do 4 | IO.puts("") 5 | end 6 | 7 | def func(par1) do 8 | IO.puts(par1) 9 | end 10 | end 11 | 12 | defmodule Callee2 do 13 | def func() do 14 | IO.puts("") 15 | end 16 | end 17 | 18 | defmodule Callee3 do 19 | def func() do 20 | IO.puts("") 21 | end 22 | end 23 | 24 | defmodule Callee4 do 25 | def func_no_arg() do 26 | IO.puts("") 27 | end 28 | 29 | def func_arg(arg \\ "") do 30 | IO.puts("" <> arg) 31 | end 32 | end 33 | 34 | defmodule Caller1 do 35 | def func() do 36 | ElixirSense.Providers.ReferencesTest.Modules.Callee1.func() 37 | end 38 | end 39 | 40 | defmodule Caller2 do 41 | def func() do 42 | ElixirSense.Providers.ReferencesTest.Modules.Callee1.func("test") 43 | end 44 | end 45 | 46 | defmodule Caller3 do 47 | def func() do 48 | "test" 49 | |> ElixirSense.Providers.ReferencesTest.Modules.Callee4.func_arg() 50 | end 51 | end 52 | 53 | defmodule Caller4 do 54 | def func() do 55 | Task.start(&ElixirSense.Providers.ReferencesTest.Modules.Callee4.func_no_arg/0) 56 | end 57 | end 58 | 59 | defmodule CallerWithAliasesAndImports do 60 | alias ElixirSense.Providers.ReferencesTest.Modules.Callee1 61 | alias ElixirSense.Providers.ReferencesTest.Modules.Callee2, as: AliasedCallee2 62 | import ElixirSense.Providers.ReferencesTest.Modules.Callee3 63 | 64 | def call_all() do 65 | [Callee1.func(), AliasedCallee2.func(), func(), Callee1.func(), Callee1.func("1")] 66 | end 67 | 68 | def call_on_different_line() do 69 | Callee3. 70 | func() 71 | end 72 | 73 | def call_erlang() do 74 | :ets.new(:a, []) 75 | end 76 | end 77 | 78 | defmodule Callee5 do 79 | def func_arg(arg \\ "") do 80 | IO.puts("" <> arg) 81 | end 82 | 83 | def func_arg1(arg \\ "") do 84 | IO.puts("" <> arg) 85 | end 86 | end 87 | 88 | defmodule Caller5 do 89 | def func() do 90 | ElixirSense.Providers.ReferencesTest.Modules.Callee5.func_arg() 91 | ElixirSense.Providers.ReferencesTest.Modules.Callee5.func_arg1("a") 92 | end 93 | end 94 | 95 | defmodule Callee6 do 96 | end 97 | 98 | defmodule Caller6 do 99 | def func() do 100 | ElixirSense.Providers.ReferencesTest.Modules.Callee6.__info__(:functions) 101 | ElixirSense.Providers.ReferencesTest.Modules.Callee6.module_info() 102 | end 103 | end 104 | 105 | defmodule Callee7 do 106 | def func_noarg() do 107 | IO.puts("") 108 | end 109 | end 110 | 111 | defmodule Caller7 do 112 | @attr ElixirSense.Providers.ReferencesTest.Modules.Callee7 113 | def func() do 114 | @attr.func_noarg() 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /test/support/options.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Options.Foo1 do 2 | @spec bar([{:option1, integer()}]) :: :ok 3 | def bar(options), do: :ok 4 | end 5 | 6 | defmodule ElixirSenseExample.Options.Foo do 7 | @spec bar([{:option1, integer()} | {:option2, String.t()}]) :: :ok 8 | def bar(options), do: :ok 9 | end 10 | 11 | defmodule ElixirSenseExample.Options.With do 12 | @spec bar(x) :: :ok when x: [{:option1, integer()} | {:option2, String.t()}] 13 | def bar(options), do: :ok 14 | end 15 | -------------------------------------------------------------------------------- /test/support/overridable_function.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.OverridableFunctions do 2 | defmacro __using__(_opts) do 3 | quote do 4 | @doc "Some overridable" 5 | @doc since: "1.2.3" 6 | @spec test(number, number) :: number 7 | def test(x, y) do 8 | x + y 9 | end 10 | 11 | defmacro required(var), do: Macro.expand(var, __CALLER__) 12 | 13 | defoverridable test: 2, required: 1 14 | end 15 | end 16 | end 17 | 18 | defmodule ElixirSenseExample.OverridableBehaviour do 19 | @callback foo :: any 20 | @macrocallback bar(any) :: Macro.t() 21 | end 22 | 23 | defmodule ElixirSenseExample.OverridableImplementation do 24 | alias ElixirSenseExample.OverridableBehaviour 25 | 26 | defmacro __using__(_opts) do 27 | quote do 28 | @behaviour OverridableBehaviour 29 | 30 | def foo do 31 | "Override me" 32 | end 33 | 34 | defmacro bar(var), do: Macro.expand(var, __CALLER__) 35 | 36 | defoverridable OverridableBehaviour 37 | end 38 | end 39 | end 40 | 41 | defmodule ElixirSenseExample.OverridableImplementation.Overrider do 42 | use ElixirSenseExample.OverridableImplementation 43 | 44 | def foo do 45 | super() 46 | end 47 | 48 | defmacro bar(any) do 49 | super(any) 50 | end 51 | end 52 | 53 | defmodule ElixirSenseExample.Overridable.Using do 54 | alias ElixirSenseExample.OverridableImplementation 55 | 56 | defmacro __using__(_opts) do 57 | quote do 58 | use OverridableImplementation 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/support/references_tracer.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSense.Core.References.Tracer do 2 | @moduledoc """ 3 | Elixir Compiler tracer that registers function calls 4 | """ 5 | use Agent 6 | 7 | @spec start_link(ElixirSense.call_trace_t()) :: Agent.on_start() 8 | def start_link(initial \\ %{}) do 9 | Agent.start_link(fn -> initial end, name: __MODULE__) 10 | end 11 | 12 | @spec get :: ElixirSense.call_trace_t() 13 | def get do 14 | Agent.get(__MODULE__, & &1) 15 | end 16 | 17 | @spec register_call(ElixirSense.call_t()) :: :ok 18 | def register_call(%{callee: callee} = call) do 19 | Agent.update(__MODULE__, fn calls -> 20 | updated_calls = 21 | case calls[callee] do 22 | nil -> [call] 23 | callee_calls -> [call | callee_calls] 24 | end 25 | 26 | calls |> Map.put(callee, updated_calls) 27 | end) 28 | end 29 | 30 | def trace({kind, meta, module, name, arity}, %Macro.Env{} = env) 31 | when kind in [:imported_function, :imported_macro, :remote_function, :remote_macro] do 32 | register_call(%{ 33 | callee: {module, name, arity}, 34 | file: env.file |> Path.relative_to_cwd(), 35 | line: meta[:line], 36 | column: meta[:column], 37 | kind: kind 38 | }) 39 | 40 | :ok 41 | end 42 | 43 | def trace({:imported_quoted, meta, module, name, arities}, %Macro.Env{} = env) do 44 | for arity <- arities do 45 | register_call(%{ 46 | callee: {module, name, arity}, 47 | file: env.file |> Path.relative_to_cwd(), 48 | line: meta[:line], 49 | column: meta[:column], 50 | kind: :imported_quoted 51 | }) 52 | end 53 | 54 | :ok 55 | end 56 | 57 | def trace({kind, meta, name, arity}, %Macro.Env{} = env) 58 | when kind in [:local_function, :local_macro] do 59 | register_call(%{ 60 | callee: {env.module, name, arity}, 61 | file: env.file |> Path.relative_to_cwd(), 62 | line: meta[:line], 63 | column: meta[:column], 64 | kind: kind 65 | }) 66 | 67 | :ok 68 | end 69 | 70 | def trace({:alias_reference, meta, module}, %Macro.Env{} = env) do 71 | register_call(%{ 72 | callee: {module, nil, nil}, 73 | file: env.file |> Path.relative_to_cwd(), 74 | line: meta[:line], 75 | column: meta[:column], 76 | kind: :alias_reference 77 | }) 78 | 79 | :ok 80 | end 81 | 82 | def trace({:alias, meta, module, _as, _opts}, %Macro.Env{} = env) do 83 | register_call(%{ 84 | callee: {module, nil, nil}, 85 | file: env.file |> Path.relative_to_cwd(), 86 | line: meta[:line], 87 | column: meta[:column], 88 | kind: :alias 89 | }) 90 | 91 | :ok 92 | end 93 | 94 | def trace({kind, meta, module, _opts}, %Macro.Env{} = env) when kind in [:import, :require] do 95 | register_call(%{ 96 | callee: {module, nil, nil}, 97 | file: env.file |> Path.relative_to_cwd(), 98 | line: meta[:line], 99 | column: meta[:column], 100 | kind: kind 101 | }) 102 | 103 | :ok 104 | end 105 | 106 | def trace(:defmodule, %Macro.Env{} = env) do 107 | register_call(%{ 108 | callee: {Kernel, :defmodule, 2}, 109 | file: env.file |> Path.relative_to_cwd(), 110 | line: env.line, 111 | column: 1, 112 | kind: :imported_macro 113 | }) 114 | 115 | :ok 116 | end 117 | 118 | def trace({:struct_expansion, meta, name, _assocs}, %Macro.Env{} = env) do 119 | register_call(%{ 120 | callee: {name, nil, nil}, 121 | file: env.file |> Path.relative_to_cwd(), 122 | line: meta[:line], 123 | column: meta[:column], 124 | kind: :struct_expansion 125 | }) 126 | 127 | :ok 128 | end 129 | 130 | def trace({:alias_expansion, meta, as, alias}, %Macro.Env{} = env) do 131 | register_call(%{ 132 | callee: {as, nil, nil}, 133 | file: env.file |> Path.relative_to_cwd(), 134 | line: meta[:line], 135 | column: meta[:column], 136 | kind: :alias_expansion_as 137 | }) 138 | 139 | register_call(%{ 140 | callee: {alias, nil, nil}, 141 | file: env.file |> Path.relative_to_cwd(), 142 | line: meta[:line], 143 | column: meta[:column], 144 | kind: :alias_expansion 145 | }) 146 | 147 | :ok 148 | end 149 | 150 | def trace(_trace, _env) do 151 | :ok 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /test/support/same_module.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.SameModule do 2 | def test_fun(), do: :ok 3 | 4 | defmacro some_test_macro() do 5 | quote do 6 | @attr "val" 7 | end 8 | end 9 | end 10 | 11 | defmodule ElixirSenseExample.SameModuleWithSecMacro do 12 | @spec some_test_macro() :: Macro.t() 13 | defmacro some_test_macro() do 14 | quote do 15 | @attr "val" 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/support/stuct_with_typespec.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.IO.Stream do 2 | defstruct [ 3 | :device, 4 | :line_or_bytes, 5 | :raw 6 | ] 7 | 8 | @type t() :: %ElixirSenseExample.IO.Stream{ 9 | device: IO.device(), 10 | line_or_bytes: :line | non_neg_integer(), 11 | raw: boolean() 12 | } 13 | end 14 | -------------------------------------------------------------------------------- /test/support/subscriber.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Subscriber do 2 | def some do 3 | ElixirSenseExample.Subscription.check("user", [:a, :b], :c) 4 | ElixirSenseExample.Subscription.check("user", [:a, :b], :c, :s) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/support/subscription.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.Subscription do 2 | def check(resource, models, user, opts \\ []) 3 | 4 | def check(nil, models, user, opts) do 5 | IO.inspect({nil, models, user, opts}) 6 | end 7 | 8 | def check(resource, models, user, opts) do 9 | IO.inspect({resource, models, user, opts}) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/support/types_with_multiple_arity.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.TypesWithMultipleArity do 2 | @typedoc "no params version" 3 | @type my_type :: integer 4 | @typedoc "one param version" 5 | @type my_type(a) :: {integer, a} 6 | @typedoc "two params version" 7 | @type my_type(a, b) :: {integer, a, b} 8 | end 9 | 10 | for i <- 1..1 do 11 | defmodule :"Elixir.ElixirSenseExample.TypesWithMultipleArity#{i}" do 12 | @typedoc "no params version" 13 | @type my_type :: integer 14 | @typedoc "one param version" 15 | @type my_type(a) :: {integer, a} 16 | @typedoc "two params version" 17 | @type my_type(a, b) :: {integer, a, b} 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/support/use_example.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirSenseExample.UseExample do 2 | defmacro __using__(_) do 3 | quote do 4 | def example do 5 | 42 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | defmodule ExUnitConfig do 2 | defp otp_older_than(major) do 3 | {otp_major_version, ""} = Integer.parse(System.build_info()[:otp_release]) 4 | otp_major_version < major 5 | end 6 | 7 | defp otp_related do 8 | for major <- 22..25, otp_older_than(major) do 9 | {:"requires_otp_#{major}", true} 10 | end 11 | end 12 | 13 | defp elixir_older_than(minor) do 14 | !Version.match?(System.build_info().version, ">= 1.#{minor}.0") 15 | end 16 | 17 | defp elixir_related do 18 | for minor_version <- 12..15, elixir_older_than(minor_version) do 19 | {:"requires_elixir_1_#{minor_version}", true} 20 | end 21 | end 22 | 23 | def erlang_eep48_supported do 24 | otp_release = System.otp_release() |> String.to_integer() 25 | otp_release >= 23 26 | end 27 | 28 | def excludes do 29 | [requires_source: true] ++ otp_related() ++ elixir_related() 30 | end 31 | end 32 | 33 | ExUnit.configure(exclude: ExUnitConfig.excludes()) 34 | ExUnit.start() 35 | 36 | Application.load(:erts) 37 | --------------------------------------------------------------------------------