-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathelixir-typecheck.js
More file actions
194 lines (165 loc) · 5.32 KB
/
elixir-typecheck.js
File metadata and controls
194 lines (165 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env node
/**
* /elixir-typecheck command wrapper
*
* Type check Elixir code with Dialyzer
*/
const ElixirCommandRunner = require('../elixir/elixir-command-runner-refactored');
async function main() {
const args = process.argv.slice(2);
const options = {};
// Parse command line arguments
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--ignore-warnings') {
options.ignoreWarnings = true;
} else if (arg === '--format') {
options.format = args[++i];
} else if (arg === '--list-unused') {
options.listUnused = true;
} else if (arg === '--verbose' || arg === '-v') {
options.verbose = true;
} else if (arg === '--help' || arg === '-h') {
showHelp();
process.exit(0);
} else if (arg.startsWith('--')) {
console.error(`Unknown option: ${arg}`);
showHelp();
process.exit(1);
}
}
try {
const runner = new ElixirCommandRunner(process.cwd());
await runner.initialize();
// Type check code
console.log('🔍 Type checking Elixir code...');
const result = await runner.typecheck(options);
if (result.success) {
console.log('\n✅ Type checking passed!');
if (result.stdout && options.verbose) {
console.log(result.stdout);
}
} else {
console.log(`\n⚠️ Type checking issues found (code: ${result.code})`);
if (result.stdout) {
console.log(result.stdout);
}
if (result.stderr) {
console.log(result.stderr);
}
// Don't exit with error if we're ignoring warnings
if (!options.ignoreWarnings) {
process.exit(result.code || 1);
}
}
} catch (error) {
console.error(`\n❌ Type checking failed: ${error.message}`);
// Check if Dialyzer/dialyxir is not installed
if (error.message.includes('Dialyzer') || error.message.includes('dialyxir')) {
console.log('\n💡 Dialyzer/dialyxir is not configured. Add to mix.exs:');
console.log(' {:dialyxir, "~> 1.4", only: [:dev], runtime: false}');
console.log('\nThen run:');
console.log(' mix deps.get');
console.log(' mix dialyzer --plt');
}
process.exit(1);
}
}
function showHelp() {
console.log(`
🔍 Elixir Type Check
Usage: /elixir-typecheck [options]
Type check Elixir code with Dialyzer.
Options:
--ignore-warnings Ignore warnings (exit with success)
--format FORMAT Output format: short, raw, etc.
--list-unused List unused functions
--verbose, -v Verbose output
--help, -h Show this help message
Examples:
/elixir-typecheck # Run type checking
/elixir-typecheck --ignore-warnings # Ignore warnings
/elixir-typecheck --format short # Short output format
/elixir-typecheck --list-unused # List unused functions
Elixir-specific features:
• Dialyzer integration via dialyxir
• Success typing analysis
• Type specification checking
• Unused function detection
• PLT (Persistent Lookup Table) caching
• Incremental analysis
Dialyzer capabilities:
• Type inference and checking
• Contract violation detection
• Dead code detection
• Unreachable code detection
• Type specification validation
• Success typing guarantees
Type specifications (@spec):
• Function type declarations
• Custom type definitions (@type)
• Opaque types (@opaque)
• Type parameterization
• Union and intersection types
• Optional and required types
Common Dialyzer warnings:
• The call will never return
• Function has no local return
• The test can never evaluate to 'true'
• Function clause will never be matched
• The pattern can never match the type
• Guard test can never succeed
mix.exs configuration:
• Add dialyxir to dev dependencies
• Configure dialyzer in config/
• Custom warning options
• PLT configuration
• Path configuration
Configuration example (config/config.exs):
config :dialyxir,
plt_file: {:no_warn, "priv/plts/dialyzer.plt"},
flags: [
:error_handling,
:race_conditions,
:underspecs,
:unknown,
:unmatched_returns
],
plt_add_apps: [:ex_unit],
ignore_warnings: "dialyzer.ignore"
Type specification examples:
@spec add(integer, integer) :: integer
def add(a, b), do: a + b
@type user_id :: integer
@type user :: %User{id: user_id, name: String.t()}
@spec get_user(user_id) :: {:ok, user} | {:error, String.t()}
def get_user(id) do
# implementation
end
PLT management:
• Build PLT with mix dialyzer --plt
• Update PLT with new dependencies
• Share PLT across projects
• Cache PLT for faster analysis
• Verify PLT integrity
Tips:
• Build PLT after adding new dependencies
• Use @spec for public API functions
• Start with basic types and refine
• Use dialyzer.ignore for false positives
• Run type checking in CI/CD
• Fix high-priority warnings first
Common patterns:
• Success typing with {:ok, result} | {:error, reason}
• Optional types with nil | type
• Union types for multiple return possibilities
• Type parameters for generic functions
• Opaque types for encapsulation
`);
}
if (require.main === module) {
main().catch((error) => {
console.error(`Fatal error: ${error.message}`);
process.exit(1);
});
}