-
-
Notifications
You must be signed in to change notification settings - Fork 82
/
Copy pathprint.html
1599 lines (1507 loc) · 105 KB
/
print.html
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>selene Documentation</title>
<meta name="robots" content="noindex">
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">selene Documentation</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="selene"><a class="header" href="#selene">selene</a></h1>
<p><strong>selene</strong> is a command line tool designed to help write correct and idiomatic Lua code.</p>
<ul>
<li><strong>New to selene?</strong> Read the <a href="./motivation.html">motivation behind its creation</a> and the <a href="./cli/index.html">CLI guide</a>. If you already use <a href="https://luacheck.readthedocs.io/en/stable/">Luacheck</a>, you can read the <a href="./luacheck.html">Luacheck comparison and migration guide</a>.</li>
<li><strong>Don't know what a linter is?</strong> Check out <a href="https://en.wikipedia.org/wiki/Lint_(software)">the wiki on linters</a> about what a linter does and how it's beneficial.</li>
<li><strong>Existing user?</strong> Read the <a href="https://github.com/Kampfkarren/selene/blob/master/CHANGELOG.md">changelog</a> to learn about any new features or bug fixes.</li>
<li><strong>Interested in what selene lints for?</strong> Read the <a href="./lints/index.html">list of lints</a>.</li>
<li><strong>Interested in contributing to selene's codebase?</strong> Read the <a href="./contributing.html">contribution guide</a>.</li>
<li><strong>Want to discuss selene or get help?</strong> Join the <a href="https://discord.gg/mhtGUS8">Roblox OS Community Discord</a>. Note: selene is not Roblox exclusive, but it started its life for Roblox development.</li>
</ul>
<h2 id="license"><a class="header" href="#license">License</a></h2>
<p>selene and all its source code are licensed under the <a href="https://www.mozilla.org/MPL/2.0/">Mozilla Public License 2.0</a>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="motivation"><a class="header" href="#motivation">Motivation</a></h1>
<h2 id="because-bugs"><a class="header" href="#because-bugs">Because bugs</a></h2>
<p>When writing any code, it's very easy to make silly mistakes that end up introducing bugs. A lot of the time, these bugs are hard to track down and debug, and sometimes are even harder to replicate.</p>
<p>This risk is made ever more real because of the generally lax nature of Lua. Incorrect code is regularly passed off and isn't noticed until something breaks at runtime. Sometimes you'll get a clear error message, and will have to spend time going back, fixing the code, and making sure you actually fixed it. Other times, the effects are more hidden, and instead of getting an error your code will just pass through along, in an incorrect state.</p>
<p>Take, for example, this code:</p>
<pre><code class="language-lua">function Player:SwapWeapons()
self.CurrentWeapon = self.SideWeapon
self.SideWeapon = self.CurrentWeapon
end
</code></pre>
<p>This is code that is technically correct, but is absolutely not what you wanted to write. However, because it is <em>technically</em> correct, Lua will do exactly what you tell it to do, and so...</p>
<ul>
<li>Player wants to swap their weapons</li>
<li>Your code calls <code>player:SwapWeapons()</code></li>
<li>Their current weapon is set to their side weapon...</li>
<li>...but their side weapon is set to their current weapon afterwards, which is what they just equipped!</li>
</ul>
<p>Uh oh! After debugging this, you realize that you actually meant to write was...</p>
<pre><code class="language-lua">function Player:SwapWeapons()
self.CurrentWeapon, self.SideWeapon = self.SideWeapon, self.CurrentWeapon
end
</code></pre>
<p>If you were using selene, you would've been alerted right away that your original code looked like an <a href="lints/almost_swapped.html"><code>almost_swapped</code></a>.</p>
<pre><code>error[almost_swapped]: this looks like you are trying to swap `self.CurrentWeapon` and `self.SideWeapon`
┌── fail.lua:4:5 ───
│
4 │ ╭ self.CurrentWeapon = self.SideWeapon
5 │ │ self.SideWeapon = self.CurrentWeapon
│ ╰────────────────────────────────────────^
│
= try: `self.CurrentWeapon, self.SideWeapon = self.SideWeapon, self.CurrentWeapon`
</code></pre>
<p>Other bugs arise because of Lua's lack of typing. While it can feel freeing to developers to not have to specify types everywhere, it makes it easier to mess up and write broken code. For example, take the following code:</p>
<pre><code class="language-lua">for _, shop in pairs(GoldShop, ItemShop, MedicineShop) do
</code></pre>
<p>This code is yet again technically correct, but not what we wanted to do. <code>pairs</code> will take the first argument, <code>GoldShop</code>, and ignore the rest. Worse, the <code>shop</code> variable will now be the values of the contents of <code>GoldShop</code>, not the shop itself. This can cause massive headaches, since although you're likely to get an error later down the line, it's more likely it'll be in the vein of "attempt to index a nil value <code>items</code>" than something more helpful. If you used <code>ipairs</code> instead of <code>pairs</code>, your code inside might just not run, and won't produce an error at all.</p>
<p>Yet again, selene saves us.</p>
<pre><code>error[incorrect_standard_library_use]: standard library function `pairs` requires 1 parameters, 3 passed
┌── fail.lua:1:16 ───
│
1 │ for _, shop in pairs(GoldShop, ItemShop, MedicineShop) do
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│
</code></pre>
<p>This clues the developer into writing the code they meant to write:</p>
<pre><code class="language-lua">for _, shop in pairs({ GoldShop, ItemShop, MedicineShop }) do
</code></pre>
<h3 id="because-idiomatic-lua"><a class="header" href="#because-idiomatic-lua">Because idiomatic Lua</a></h3>
<p>While it's nice to write code however you want to, issues can arise when you are working with other people, or plan on open sourcing your work for others to contribute to. It's best for everyone involved if they stuck to the same way of writing Lua.</p>
<p>Consider this contrived example:</p>
<pre><code class="language-lua">call(1 / 0)
</code></pre>
<p>The person who wrote this code might have known that <code>1 / 0</code> evaluates to <code>math.huge</code>. However, anyone working on that code will likely see it and spend some time figuring out why they wrote the code that way.</p>
<p>If the developer was using selene, this code would be denied:</p>
<pre><code>warning[divide_by_zero]: dividing by zero is not allowed, use math.huge instead
┌── fail.lua:1:6 ───
│
1 │ call(1 / 0)
│ ^^^^^
│
</code></pre>
<p>Furthermore, selene is meant to be easy for developers to add their own lints to. You could create your own lints for your team to prevent behavior that is non-idiomatic to the codebase. For example, let's say you're working on a <a href="https://developer.roblox.com/en-us">Roblox</a> codebase, and you don't want your developers using the data storage methods directly. You could create your own lint so that this code:</p>
<pre><code class="language-lua">local DataStoreService = game:GetService("DataStoreService")
</code></pre>
<p>...creates a warning, discouraging its use. For more information on how to create your own lints, check out the <a href="contributing.html">contribution guide</a>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="luacheck-comparison"><a class="header" href="#luacheck-comparison">Luacheck Comparison</a></h1>
<h2 id="selene-vs-luacheck"><a class="header" href="#selene-vs-luacheck">selene vs. luacheck</a></h2>
<p>selene is not the first Lua linter. The main inspiration behind selene is <a href="https://luacheck.readthedocs.io/en/stable/">luacheck</a>. However, the two have very little in common besides inception.</p>
<ul>
<li>selene is actively maintained, while at the time of writing luacheck's last commit was in October 2018.</li>
<li>selene is written in Rust, while luacheck is written in Lua. In practice, this means that selene is much faster than luacheck while also being able to easily take advantage of features luacheck cannot because of the difficulty of using dependencies in Lua.</li>
<li>selene is multithreaded, again leading to significantly better performance.</li>
<li>selene has rich output, while luacheck has basic output.</li>
</ul>
<p>selene:</p>
<pre><code>error[suspicious_reverse_loop]: this loop will only ever run once at most
┌── fail.lua:1:9 ───
│
1 │ for _ = #x, 1 do
│ ^^^^^
│
= help: try adding `, -1` after `1`
</code></pre>
<p>luacheck:</p>
<pre><code>Checking fail.lua 2 warnings
fail.lua:1:1: numeric for loop goes from #(expr) down to 1 but loop step is not negative
</code></pre>
<ul>
<li>selene uses <a href="https://github.com/toml-lang/toml">TOML</a> files for configuration, while luacheck uses <code>.luacheckrc</code>, which runs Lua.</li>
<li>selene allows for <a href="./usage/std.html">standard library configuration</a> such as argument types, argument counts, etc, while luacheck only allows knowing that fields exist and can be written to. In practice, this means that selene catches:</li>
</ul>
<pre><code class="language-lua">for _, shop in pairs(GoldShop, ItemShop, MedicineShop) do
math.pi()
</code></pre>
<p>...while luacheck does not.</p>
<ul>
<li>selene has English names for lints instead of arbitrary numbers. In luacheck, you ignore "<a href="https://luacheck.readthedocs.io/en/stable/warnings.html#unbalanced-assignments"><code>211</code></a>", while in selene you ignore "<a href="./lints/unbalanced_assignments.html"><code>unbalanced_assignments</code></a>".</li>
<li>selene has distinctions for "deny" and "warn", while every luacheck lint is the same.</li>
<li>selene has a much simpler codebase, and is much easier to add your own lints to.</li>
<li>selene has optional support and large focus specifically for Roblox development.</li>
<li>selene will only show you files that lint, luacheck only does this with the <code>-q</code> option (quiet).</li>
<li>selene <a href="./usage/filtering.html">filters specific lints</a> and applies over code rather than lines, luacheck does not.</li>
<li>selene has <a href="./lints/index.html">significantly more lints</a>.</li>
</ul>
<p>This is not to say selene is objectively better than luacheck, at least not yet.</p>
<ul>
<li>luacheck has lints for long lines and whitespace issues, selene does not as it is unclear whether style issues like these are fit for a linter or better under the scope of a Lua beautifier.</li>
<li>luacheck officially supports versions past Lua 5.1, selene does not yet as there is not much demand.</li>
<li>luacheck supports the following lints that selene does not yet:
<ul>
<li>Unreachable code</li>
<li>Unused labels (selene does not officially support Lua 5.2 yet)</li>
<li>Detecting variables that are only ever mutated, but not read</li>
<li>Using uninitialized variables</li>
</ul>
</li>
</ul>
<h2 id="migration"><a class="header" href="#migration">Migration</a></h2>
<p>luacheck does not require much configuration to begin with, so migration should be easy.</p>
<ul>
<li>You can configure what lints are allowed in the <a href="./usage/configuration.html#changing-the-severity-of-lints">configuration</a>.</li>
<li>Do you have a custom standard library (custom globals, functions, etc)? Read the <a href="./usage/std.html">standard library guide</a>.
<ul>
<li>Are you a Roblox developer using something like <a href="https://github.com/Quenty/luacheck-roblox/">luacheck-roblox</a>? A featureful standard library for Roblox is generated with every commit on GitHub. TODO: Have a flag in the selene CLI to generate a Roblox standard library a la <code>generate-roblox-std</code>? Should <code>generate-roblox-std</code> be uploaded to crates.io?</li>
</ul>
</li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h1 id="command-line-interface"><a class="header" href="#command-line-interface">Command Line Interface</a></h1>
<p><strong>selene</strong> is mostly intended for use as a command line tool.</p>
<p>In this section, you will learn how to use selene in this manner.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="installation"><a class="header" href="#installation">Installation</a></h1>
<p><strong>selene</strong> is written in Rust, and the recommended installation method is through the <a href="https://doc.rust-lang.org/cargo/">Cargo package manager</a>.</p>
<p>To use Cargo, you must first install Rust. <a href="https://rustup.rs/">rustup.rs</a> is a tool that makes this very easy.</p>
<p>Once you have Rust installed, use either command:</p>
<p><strong>If you want the most stable version of selene</strong></p>
<pre><code>cargo install selene
</code></pre>
<p><strong>If you want the most up to date version of selene</strong></p>
<pre><code>cargo install --branch main --git https://github.com/Kampfkarren/selene selene
</code></pre>
<h3 id="disabling-roblox-features"><a class="header" href="#disabling-roblox-features">Disabling Roblox features</a></h3>
<p>selene is built with Roblox specific lints by default. If you don't want these, then pass <code>--no-default-features</code> to the <code>cargo install</code> command.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="cli-usage"><a class="header" href="#cli-usage">CLI Usage</a></h1>
<p>If you want to get a quick understanding of the interface, simply type <code>selene --help</code>.</p>
<pre><code>USAGE:
selene [FLAGS] [OPTIONS] <files>...
selene <SUBCOMMAND>
FLAGS:
--allow-warnings Pass when only warnings occur
--no-exclude Ignore excludes defined in config
-h, --help Prints help information
-n, --no-summary Suppress summary information
-q, --quiet Display only the necessary information. Equivalent to --display-style="quiet"
-V, --version Prints version information
OPTIONS:
--color <color> [default: auto] [possible values: Always, Auto, Never]
--config <config> A toml file to configure the behavior of selene [default: selene.toml]
--display-style <display-style> Sets the display method [possible values: Json, Json2, Rich, Quiet]
--num-threads <num-threads> Number of threads to run on, default to the numbers of logical cores on your
system [default: your system's cores]
--pattern <pattern> A glob to match files with to check
ARGS:
<files>...
SUBCOMMANDS:
generate-roblox-std
help Prints this message or the help of the given subcommand(s)
update-roblox-std
upgrade-std
</code></pre>
<h2 id="basic-usage"><a class="header" href="#basic-usage">Basic usage</a></h2>
<p>All unnamed inputs you give to selene will be treated as files to check for.</p>
<p>If you want to check a folder of files: <code>selene files</code></p>
<p>If you just want to check one file: <code>selene code.lua</code></p>
<p>If you want to check multiple files/folders: <code>selene file1 file2 file3 ...</code></p>
<p>If you want to pipe code to selene using stdin: <code>cat code.lua | selene -</code></p>
<h2 id="advanced-options"><a class="header" href="#advanced-options">Advanced options</a></h2>
<p><strong>-q</strong></p>
<p><strong>--quiet</strong></p>
<p>Instead of the rich format, only necessary information will be displayed.</p>
<pre><code>~# selene code.lua
warning[divide_by_zero]: dividing by zero is not allowed, use math.huge instead
┌── code.lua:1:6 ───
│
1 │ call(1 / 0)
│ ^^^^^
│
Results:
0 errors
1 warnings
0 parse errors
~# selene code.lua -q
code.lua:1:6: warning[divide_by_zero]: dividing by zero is not allowed, use math.huge instead
Results:
0 errors
1 warnings
0 parse errors
</code></pre>
<p><strong>--num-threads</strong> <em>num-threads</em></p>
<p>Specifies the number of threads for selene to use. Defaults to however many cores your CPU has. If you type <code>selene --help</code>, you can see this number because it will show as the default for you.</p>
<p><strong>--pattern</strong> <em>pattern</em></p>
<p>A <a href="https://en.wikipedia.org/wiki/Glob_(programming)">glob</a> to match what files selene should check for. For example, if you only wanted to check files that end with <code>.spec.lua</code>, you would input <code>--pattern **/*.spec.lua</code>. Defaults to <code>**/*.lua</code>, meaning "any lua file", or <code>**/*.lua</code> and <code>**/*.luau</code> with the roblox feature flag, meaning "any lua/luau file".</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="usage"><a class="header" href="#usage">Usage</a></h1>
<p>In this section, you will learn how to interact with selene from your code and how to fit it to your liking.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="configuration"><a class="header" href="#configuration">Configuration</a></h1>
<p>selene is meant to be easily configurable. You can specify configurations for the entire project as well as for individual lints.</p>
<p>Configuration files are placed in the directory you are running selene in and are named <strong>selene.toml</strong>. As the name suggests, the configurations use the <a href="https://github.com/toml-lang/toml">Tom's Obvious, Minimal Language (TOML)</a> format. It is recommended you quickly brush up on the syntax, though it is very easy.</p>
<h2 id="changing-the-severity-of-lints"><a class="header" href="#changing-the-severity-of-lints">Changing the severity of lints</a></h2>
<p>You can change the severity of lints by entering the following into selene.toml:</p>
<pre><code class="language-toml">[lints]
lint_1 = "severity"
lint_2 = "severity"
...
</code></pre>
<p>Where "severity" is one of the following:</p>
<ul>
<li><code>"allow"</code> - Don't check for this lint</li>
<li><code>"warn"</code> - Warn for this lint</li>
<li><code>"deny"</code> - Error for this lint</li>
</ul>
<p>Note that "deny" and "warn" are effectively the same, only warn will give orange text while error gives red text, and they both have different counters.</p>
<h2 id="configuring-specific-lints"><a class="header" href="#configuring-specific-lints">Configuring specific lints</a></h2>
<p>You can configure specific lints by entering the following into selene.toml:</p>
<pre><code class="language-toml">[config]
lint1 = ...
lint2 = ...
...
</code></pre>
<p>Where the value is whatever the special configuration of that lint is. You can learn these on the lints specific page in the <a href="usage/../lints/index.html">list of lints</a>. For example, if we wanted to allow empty if branches if the contents contain comments, then we would write:</p>
<pre><code class="language-toml">[config]
empty_if = { comments_count = true }
</code></pre>
<h2 id="setting-the-standard-library"><a class="header" href="#setting-the-standard-library">Setting the standard library</a></h2>
<p>Many lints use standard libraries for either verifying their correct usage or for knowing that variables exist where they otherwise wouldn't.</p>
<p>By default, selene uses Lua 5.1, though if we wanted to use the Lua 5.2 standard library, we would write:</p>
<pre><code class="language-toml">std = "lua52"
</code></pre>
<p>...at the top of selene.toml. You can learn more about the standard library format on the <a href="usage/./std.html">standard library guide</a>. The standard library given can either be one of the builtin ones (currently only <code>lua51</code> and <code>lua52</code>) or the filename of a standard library file in this format. For example, if we had a file named <code>special.toml</code>, we would write:</p>
<pre><code class="language-toml">std = "special"
</code></pre>
<h3 id="chaining-the-standard-library"><a class="header" href="#chaining-the-standard-library">Chaining the standard library</a></h3>
<p>We can chain together multiple standard libraries by simply using a plus sign (<code>+</code>) in between the names.</p>
<p>For example, if we had <code>game.toml</code> and <code>engine.toml</code> standard libraries, we could chain them together like so:</p>
<pre><code class="language-toml">std = "game+engine"
</code></pre>
<h3 id="excluding-files-from-being-linted"><a class="header" href="#excluding-files-from-being-linted">Excluding files from being linted</a></h3>
<p>It is possible to exclude files from being linted using the exclude option:</p>
<pre><code class="language-toml">exclude = ["external/*", "*.spec.lua"]
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="filtering"><a class="header" href="#filtering">Filtering</a></h1>
<p>Lints can be toggled on and off in the middle of code when necessary through the use of special comments.</p>
<h2 id="allowingdenying-lints-for-a-piece-of-code"><a class="header" href="#allowingdenying-lints-for-a-piece-of-code">Allowing/denying lints for a piece of code</a></h2>
<p>Suppose we have the following code:</p>
<pre><code class="language-lua">local something = 1
</code></pre>
<p>selene will correctly attribute this as an unused variable:</p>
<pre><code>warning[unused_variable]: something is assigned a value, but never used
┌── code.lua:1:7 ───
│
1 │ local something = 1
│ ^^^^^^^^^
│
</code></pre>
<p>However, perhaps we as the programmer have some reason for leaving this unused (and not renaming it to <code>_something</code>). This would be where inline lint filtering comes into play. In this case, we would simply write:</p>
<pre><code class="language-lua">-- selene: allow(unused_variable)
local something = 1
</code></pre>
<p>This also works with settings other than <code>allow</code>--you can <code>warn</code> or <code>deny</code> lints in the same fashion. For example, you can have a project with the following <code>selene.toml</code> <a href="usage/./configuration.html">configuration</a>:</p>
<pre><code class="language-toml">[lints]
unused_variable = "allow" # I'm fine with unused variables in code
</code></pre>
<p>...and have this in a separate file:</p>
<pre><code class="language-lua">-- I'm usually okay with unused variables, but not this one
-- selene: deny(unused_variable)
local something = 1
</code></pre>
<p>This is applied to the entire piece of code its near, <em>not</em> just the next line. For example:</p>
<pre><code class="language-lua">-- selene: allow(unused_variable)
do
local foo = 1
local bar = 2
end
</code></pre>
<p>...will silence the unused variable warning for both <code>foo</code> and <code>bar</code>.</p>
<h2 id="allowingdenying-lints-for-an-entire-file"><a class="header" href="#allowingdenying-lints-for-an-entire-file">Allowing/denying lints for an entire file</a></h2>
<p>If you want to allow/deny a lint for an entire file, you can do this by attaching the following code to the beginning:</p>
<pre><code class="language-lua">--# selene: allow(lint_name)
</code></pre>
<p>The <code>#</code> tells selene that you want to apply these globally.</p>
<p>These <em>must</em> be before any code, otherwise selene will deny it. For example, the following code:</p>
<pre><code class="language-lua">local x = 1
--# selene: allow(unused_variable)
</code></pre>
<p>...will cause selene to error:</p>
<pre><code>warning[unused_variable]: x is assigned a value, but never used
┌─ -:1:7
│
1 │ local x = 1
│ ^
error[invalid_lint_filter]: global filters must come before any code
┌─ -:1:1
│
1 │ local x = 1
│ ----------- global filter must be before this
2 │ --# selene: allow(unused_variable)
</code></pre>
<h2 id="combining-multiple-lints"><a class="header" href="#combining-multiple-lints">Combining multiple lints</a></h2>
<p>You can filter multiple lints in two ways:</p>
<pre><code class="language-lua">-- selene: allow(lint_one)
-- selene: allow(lint_two)
-- or...
-- selene: allow(lint_one, lint_two)
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="standard-library-format"><a class="header" href="#standard-library-format">Standard Library Format</a></h1>
<p>selene provides a robust standard library format to allow for use with environments other than vanilla Lua. Standard libraries are defined in the form of <a href="https://en.wikipedia.org/wiki/YAML">YAML</a> files.</p>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
<p>For examples of the standard library format, see:</p>
<ul>
<li><a href="https://github.com/Kampfkarren/selene/blob/main/selene-lib/default_std/lua51.yml"><code>lua51.yml</code></a> - The default standard library for Lua 5.1</li>
<li><a href="https://github.com/Kampfkarren/selene/blob/main/selene-lib/default_std/lua52.yml"><code>lua52.yml</code></a> - A standard library for Lua 5.2's additions and removals. Reference this if your standard library is based off another (it most likely is).</li>
<li><a href="https://gist.github.com/Kampfkarren/dff2dc17cc30d68a48510da58fff2381"><code>roblox.yml</code></a> - A standard library for Roblox that incorporates all the advanced features of the format. If you are a Roblox developer, don't use this as anything other than reference--an up to date version of this library is automatically generated.</li>
</ul>
<h2 id="base"><a class="header" href="#base">base</a></h2>
<p>Used for specifying what standard library to be based off of. This supports both builtin libraries (lua51, lua52, lua53, lua54, roblox), as well as any standard libraries that can be found in the current directory.</p>
<pre><code class="language-yaml">--- # This begins a YAML file
base: lua51 # We will be extending off of Lua 5.1.
</code></pre>
<h2 id="lua_versions"><a class="header" href="#lua_versions">lua_versions</a></h2>
<p>Used for specifying the versions of Lua you support for the purpose of supporting the syntax of those dialects. If empty, will default to 5.1.</p>
<p>Supports the following options:</p>
<ul>
<li><code>lua51</code> - Lua 5.1</li>
<li><code>lua52</code> - Lua 5.2</li>
<li><code>lua53</code> - Lua 5.3</li>
<li><code>lua54</code> - Lua 5.4</li>
<li><code>luau</code> - <a href="https://luau-lang.org">Luau</a></li>
<li><code>luajit</code> - LuaJIT</li>
</ul>
<p>Usually you only need to specify one--for example, <code>lua54</code> will give Lua 5.4 syntax and all versions prior. That means that if you specify it, it will look something like:</p>
<pre><code class="language-yml">lua_versions:
- luajit
</code></pre>
<p>If you are extending off a library that specifies it (like <code>lua51</code>, etc) then you do not need this. If you specify it while overriding a library, it will override it.</p>
<h2 id="globals"><a class="header" href="#globals">globals</a></h2>
<p>This is where the magic happens. The <code>globals</code> field is a dictionary where the keys are the globals you want to define. The value you give tells selene what the value can be, do, and provide.</p>
<p>If your standard library is based off another, overriding something defined there will use your implementation over the original.</p>
<h3 id="any"><a class="header" href="#any">Any</a></h3>
<pre><code class="language-yaml">---
globals:
foo:
any: true
</code></pre>
<p>This specifies that the field can be used in any possible way, meaning that <code>foo.x</code>, <code>foo:y()</code>, etc will all validate.</p>
<h3 id="functions"><a class="header" href="#functions">Functions</a></h3>
<pre><code class="language-yaml">---
globals:
tonumber:
args:
- type: any
- type: number
required: false
</code></pre>
<p>A field is a function if it contains an <code>args</code> and/or <code>method</code> field.</p>
<p>If <code>method</code> is specified as <code>true</code> and the function is inside a table, then it will require the function be called in the form of <code>Table:FunctionName()</code>, instead of <code>Table.FunctionName()</code>.</p>
<p><code>args</code> is an array of arguments, in order of how they're used in the function. An argument is in the form of:</p>
<pre><code>required?: false | true | string;
type: "any" | "bool" | "function" | "nil"
| "number" | "string" | "table" | "..."
| string[] | { "display": string }
</code></pre>
<h4 id="required"><a class="header" href="#required">"required"</a></h4>
<ul>
<li><code>true</code> - The default, this argument is required.</li>
<li><code>false</code> - This argument is optional.</li>
<li>A string - This argument is required, and not using it will give this as the reason why.</li>
</ul>
<h4 id="observes"><a class="header" href="#observes">"observes"</a></h4>
<p>This field is used for allowing smarter introspection of how the argument given is used.</p>
<ul>
<li>"read-write" - The default. This argument is potentially both written to and read from.</li>
<li>"read" - This argument is only read from. Currently unused.</li>
<li>"write" - This argument is only written to. Used by <code>unused_variable</code> to assist in detecting a variable only being written to, even if passed into a function.</li>
</ul>
<p>Example:</p>
<pre><code class="language-yml"> table.insert:
args:
- type: table
observes: write # This way, `table.insert(x, 1)` doesn't count as a read to `x`
- type: any
- required: false
type: any
</code></pre>
<h4 id="must_use"><a class="header" href="#must_use">"must_use"</a></h4>
<p>This field is used for checking if the return value of a function is used.</p>
<ul>
<li><code>false</code> - The default. The return value of this function does not need to be used.</li>
<li><code>true</code> - The return value of this function must be used.</li>
</ul>
<p>Example:</p>
<pre><code class="language-yml"> tostring:
args:
- type: any
must_use: true
</code></pre>
<h4 id="argument-types"><a class="header" href="#argument-types">Argument types</a></h4>
<ul>
<li><code>"any"</code> - Allows any value.</li>
<li><code>"bool"</code>, <code>"function"</code>, <code>"nil"</code>, <code>"number"</code>, <code>"string"</code>, <code>"table"</code> - Expects a value of the respective type.</li>
<li><code>"..."</code> - Allows any number of variables after this one. If <code>required</code> is true (it is by default), then this will lint if no additional arguments are given. It is incorrect to have this in the middle.</li>
<li>Constant list of strings - Will check if the value provided is one of the strings in the list. For example, <code>collectgarbage</code> only takes one of a few exact string arguments--doing <code>collectgarbage("count")</code> will work, but <code>collectgarbage("whoops")</code> won't.</li>
<li><code>{ "display": string }</code> - Used when no constant could possibly be correct. If a constant is used, selene will tell the user that an argument of the type (display) is required. For an example, the Roblox method <code>Color3.toHSV</code> expects a <code>Color3</code> object--no constant inside it could be correct, so this is defined as:</li>
</ul>
<pre><code class="language-yaml">---
globals:
Color3.toHSV:
args:
- type:
display: Color3
</code></pre>
<h3 id="properties"><a class="header" href="#properties">Properties</a></h3>
<pre><code class="language-yaml">---
globals:
_VERSION:
property: read-only
</code></pre>
<p>Specifies that a property exists. For example, <code>_VERSION</code> is available as a global and doesn't have any fields of its own, so it is just defined as a property.</p>
<p>The same goes for <code>_G</code>, which is defined as:</p>
<pre><code class="language-yaml">_G:
property: new-fields
</code></pre>
<p>The value of property tells selene how it can be mutated and used:</p>
<ul>
<li><code>"read-only"</code> - New fields cannot be added or set, and the variable itself cannot be redefined.</li>
<li><code>"new-fields"</code> - New fields can be added and set, but variable itself cannot be redefined. In the case of _G, it means that <code>_G = "foo"</code> is linted against.</li>
<li><code>"override-fields"</code> - New fields can't be added, but entire variable can be overridden. In the case of Roblox's <code>Instance.Name</code>, it means we can do <code>Instance.Name = "Hello"</code>, but not <code>Instance.Name.Call()</code>.</li>
<li><code>"full-write"</code> - New fields can be added and entire variable can be overridden.</li>
</ul>
<h3 id="struct"><a class="header" href="#struct">Struct</a></h3>
<pre><code class="language-yaml">---
globals:
game:
struct: DataModel
</code></pre>
<p>Specifies that the field is an instance of a <a href="usage/std.html#structs">struct</a>. The value is the name of the struct.</p>
<h3 id="tables"><a class="header" href="#tables">Tables</a></h3>
<pre><code class="language-yaml">---
globals:
math.huge:
property: read-only
math.pi:
property: read-only
</code></pre>
<p>A field is understood as a table if it has fields of its own. Notice that <code>math</code> is not defined anywhere, but its fields are. This will create an implicit <code>math</code> with the property writability of <code>read-only</code>.</p>
<h3 id="deprecated"><a class="header" href="#deprecated">Deprecated</a></h3>
<p>Any field or arg can have a deprecation notice added to it, which will then be read by <a href="usage/../lints/deprecated.html">the deprecated lint</a>.</p>
<pre><code class="language-yaml">---
globals:
table.getn:
args:
- type: table
- type: number
deprecated:
message: "`table.getn` has been superseded by #."
replace:
- "#%1"
</code></pre>
<p>The deprecated field consists of two subfields.</p>
<p><code>message</code> is required, and is a human readable explanation of what the deprecation is, and potentially why.</p>
<p><code>replace</code> is an optional array of replacements. The most relevant replacement is suggested to the user. If used with a function, then every parameter of the function will be provided.</p>
<p>For instance, since <code>table.getn</code>'s top replacement is <code>#%1</code>:</p>
<ul>
<li><code>table.getn(x)</code> will suggest <code>#x</code></li>
<li><code>table.getn()</code> will not suggest anything, as there is no relevant suggestion</li>
</ul>
<p>You can also use <code>%...</code> to list every argument, separated by commas.</p>
<p>The following:</p>
<pre><code class="language-yaml">---
globals:
call:
deprecated:
message: "call will be removed in the next version"
replace:
- "newcall(%...)"
args:
- type: "..."
required: false
</code></pre>
<p>...will suggest <code>newcall(1, 2, 3)</code> for <code>call(1, 2, 3)</code>, and <code>newcall()</code> for <code>call()</code>.</p>
<p>You can also use <code>%%</code> to write a raw <code>%</code>.</p>
<h3 id="removed"><a class="header" href="#removed">Removed</a></h3>
<pre><code class="language-yaml">---
globals:
getfenv:
removed: true
</code></pre>
<p>Used when your standard library is <a href="usage/std.html#base">based off</a> another, and your library removes something from the original.</p>
<h2 id="structs"><a class="header" href="#structs">Structs</a></h2>
<p>Structs are used in places such as Roblox Instances. Every Instance in Roblox, for example, declares a <code>:GetChildren()</code> method. We don't want to have to define this everywhere an Instance is declared globally, so instead we just define it once in a struct.</p>
<p>Structs are defined as fields of <code>structs</code>. Any fields they have will be used for instances of that struct. For example, the Roblox standard library has the struct:</p>
<pre><code class="language-yaml">---
structs:
Event:
Connect:
method: true
args:
- type: function
</code></pre>
<p>From there, it can define:</p>
<pre><code class="language-yaml">globals:
workspace.Changed:
struct: Event
</code></pre>
<p>...and selene will know that <code>workspace.Changed:Connect(callback)</code> is valid, but <code>workspace.Changed:RandomNameHere()</code> is not.</p>
<h2 id="wildcards"><a class="header" href="#wildcards">Wildcards</a></h2>
<p>Fields can specify requirements if a field is referenced that is not explicitly named. For example, in Roblox, instances can have arbitrary fields of other instances (<code>workspace.Baseplate</code> indexes an instance named Baseplate inside <code>workspace</code>, but <code>Baseplate</code> is nowhere in the Roblox API).</p>
<p>We can specify this behavior by using the special <code>"*"</code> field.</p>
<pre><code class="language-yaml">workspace.*:
struct: Instance
</code></pre>
<p>This will tell selene "any field accessed from <code>workspace</code> that doesn't exist must be an Instance <a href="usage/std.html#structs">struct</a>".</p>
<p>Wildcards can even be used in succession. For example, consider the following:</p>
<pre><code class="language-yaml">script.Name:
property: override-fields
script.*.*:
property: full-write
</code></pre>
<p>Ignoring the wildcard, so far this means:</p>
<ul>
<li><code>script.Name = "Hello"</code> <em>will</em> work.</li>
<li><code>script = nil</code> <em>will not</em> work, because the writability of <code>script</code> is not specified.</li>
<li><code>script.Name.UhOh</code> <em>will not</em> work, because <code>script.Name</code> does not have fields.</li>
</ul>
<p>However, with the wildcard, this adds extra meaning:</p>
<ul>
<li><code>script.Foo = 3</code> <em>will not</em> work, because the writability of <code>script.*</code> is not specified.</li>
<li><code>script.Foo.Bar = 3</code> <em>will</em> work, because <code>script.*.*</code> has full writability.</li>
<li><code>script.Foo.Bar.Baz = 3</code> <em>will</em> work for the same reason as above.</li>
</ul>
<h2 id="internal-properties"><a class="header" href="#internal-properties">Internal properties</a></h2>
<p>There are some properties that exist in standard library YAMLs that exist specifically for internal purposes. This is merely a reference, but these are not guaranteed to be stable.</p>
<h3 id="name"><a class="header" href="#name">name</a></h3>
<p>This specifies the name of the standard library. This is used internally for cases such as only giving Roblox lints if the standard library is named <code>"roblox"</code>.</p>
<h3 id="last_updated"><a class="header" href="#last_updated">last_updated</a></h3>
<p>A timestamp of when the standard library was last updated. This is used by the Roblox standard library generator to update when it gets too old.</p>
<h3 id="last_selene_version"><a class="header" href="#last_selene_version">last_selene_version</a></h3>
<p>A timestamp of the last selene version that generated this standard library. This is used by the Roblox standard library generator to update when it gets too old.</p>
<h3 id="roblox_classes"><a class="header" href="#roblox_classes">roblox_classes</a></h3>
<p>A map of every Roblox class and their properties, for <a href="usage/../lints/roblox_incorrect_roact_usage.html">roblox_incorrect_roact_usage</a>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="roblox-guide"><a class="header" href="#roblox-guide">Roblox Guide</a></h1>
<p>selene is built with Roblox development in mind, and has special features for Roblox developers.</p>
<p>If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "<code>game</code> is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox <a href="./usage/configuration.html">standard library</a> in order to fix these issues, as well as get Roblox specific lints.</p>
<h2 id="installation-1"><a class="header" href="#installation-1">Installation</a></h2>
<p>Thankfully, this process is very simple. All you need to do is edit your <code>selene.toml</code> (or create one) and add the following:</p>
<pre><code class="language-toml">std = "roblox"
</code></pre>
<p>The next time you run selene, or if you use the Visual Studio Code extension and start typing Lua code, a Roblox standard library will be automatically generated and used. This is an automatic process that occurs whenever you don't have a cached standard library file and your <code>selene.toml</code> has <code>std = "roblox"</code>.</p>
<h2 id="updating-definitions"><a class="header" href="#updating-definitions">Updating definitions</a></h2>
<p>The Roblox standard library file is updated automatically every 6 hours. If you need an update faster than that, you can run <code>selene update-roblox-std</code> manually.</p>
<h2 id="testez-support"><a class="header" href="#testez-support">TestEZ Support</a></h2>
<p>Roblox has provided an open source testing utility called <a href="https://roblox.github.io/testez/">TestEZ</a>, which allows you to write unit tests for your code. Writing unit tests is good practice, but selene will get angry at you if you don't include a <code>testez.yml</code> file and set the standard library to the following:</p>
<p><code>std = "roblox+testez"</code></p>
<p>But first you'll need to create a <code>testez.yml</code> file, which you can do so <a href="https://gist.github.com/Kampfkarren/f2dddc2ebfa4e0662e44b8702e519c2d">with this template</a>.</p>
<h2 id="pinned-standard-library"><a class="header" href="#pinned-standard-library">Pinned standard library</a></h2>
<p>There may be cases where you would rather not have selene automatically update the Roblox standard library, such as if speed is critically important and you want to limit potential internet access (generating the standard library requires an active internet connection).</p>
<p>selene supports "pinning" the standard library to a specific version.</p>
<p>Add the following to your <code>selene.toml</code> configuration:</p>
<pre><code class="language-toml"># `floating` by default, meaning it is stored in a cache folder on your system
roblox-std-source = "pinned"
</code></pre>
<p>This will generate the standard library file into <code>roblox.yml</code> where it is run.</p>
<p>You can also create a <code>roblox.yml</code> file manually with <code>selene generate-roblox-std</code>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="contributing"><a class="header" href="#contributing">Contributing</a></h1>
<p>selene is written in Rust, so knowledge of the ecosystem is expected.</p>
<p>selene uses <a href="https://github.com/Kampfkarren/full-moon">Full Moon</a> to parse the Lua code losslessly, meaning whitespace and comments are preserved. You can read the full documentation for full-moon on its <a href="https://docs.rs/full_moon/latest/full_moon/">docs.rs</a> page.</p>
<p>TODO: Upload selene-lib on crates.io and link the docs.rs page for that as well as throughout the rest of this article.</p>
<h2 id="writing-a-lint"><a class="header" href="#writing-a-lint">Writing a lint</a></h2>
<p>In selene, lints are created in isolated modules. To start, create a file in <code>selene-lib/src/lints</code> with the name of your lint. In this example, we're going to call the lint <code>cool_lint.rs</code>.</p>
<p>Let's now understand what a lint consists of. selene takes lints in the form of structs that implement the <code>Lint</code> trait. The <code>Lint</code> trait expects:</p>
<ul>
<li>A <code>Config</code> associated type that defines what the configuration format is expected to be. Whatever you pass must be <a href="https://serde.rs/">deserializable</a>.</li>
<li>An <code>Error</code> associated type that implements <a href="https://doc.rust-lang.org/std/error/trait.Error.html"><code>std::error::Error</code></a>. This is used if configurations can be invalid (such as a parameter only being a number within a range). Most of the time, configurations cannot be invalid (other than deserializing errors, which are handled by selene), and so you can set this to <a href="https://doc.rust-lang.org/std/convert/enum.Infallible.html"><code>std::convert::Infallible</code></a>.</li>
<li>A <code>SEVERITY</code> constant which is either <code>Severity::Error</code> or <code>Severity::Warning</code>. Use <code>Error</code> if the code is positively impossible to be correct.</li>
<li>A <code>LINT_TYPE</code> constant which is either <code>Complexity</code>, <code>Correctness</code>, <code>Performance</code>, or <code>Style</code>. So far not used for anything.</li>
<li>A <code>new</code> function with the signature <code>fn new(config: Self::Config) -> Result<Self, Self::Error></code>. With the selene CLI, this is called once.</li>
<li>A <code>pass</code> function with the signature <code>fn pass(&self, ast: &full_moon::ast::Ast, context: &Context, ast_context: &AstContext) -> Vec<Diagnostic></code>. The <code>ast</code> argument is the full-moon representation of the code. The <code>context</code> argument provides optional additional information, such as the standard library being used. The <code>ast_context</code> argument provides context specific to that AST, such as its scopes. Any <code>Diagnostic</code> structs returned here are displayed to the user.</li>
</ul>
<p>For our purposes, we're going to write:</p>
<pre><code class="language-rs">use super::*;
use std::convert::Infallible;
struct CoolLint;
impl Lint for CoolLint {
type Config = ();
type Error = Infallible;
const SEVERITY: Severity = Severity::Warning;
const LINT_TYPE: LintType = LintType::Style;
fn new(_: Self::Config) -> Result<Self, Self::Error> {
Ok(CoolLint)
}
fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
unimplemented!()
}
}
</code></pre>
<p>The implementation of <code>pass</code> is completely up to you, but there are a few common patterns.</p>
<ul>
<li>Creating a visitor over the ast provided and creating diagnostics based off of that. See <a href="https://github.com/Kampfkarren/selene/blob/master/selene-lib/src/lints/divide_by_zero.rs"><code>divide_by_zero</code></a> and <a href="https://github.com/Kampfkarren/selene/blob/master/selene-lib/src/lints/suspicious_reverse_loop.rs"><code>suspicious_reverse_loop</code></a> for straight forward examples.</li>
<li>Using the <code>ScopeManager</code> struct to lint based off of usage of variables and references. See <a href="https://github.com/Kampfkarren/selene/blob/master/selene-lib/src/lints/shadowing.rs"><code>shadowing</code></a> and <a href="https://github.com/Kampfkarren/selene/blob/master/selene-lib/src/lints/global_usage.rs"><code>global_usage</code></a>.</li>
</ul>
<h3 id="getting-selene-to-recognize-the-new-lint"><a class="header" href="#getting-selene-to-recognize-the-new-lint">Getting selene to recognize the new lint</a></h3>
<p>Now that we have our lint, we have to make sure selene actually knows to use it. There are two places you need to update.</p>
<p>In selene-lib/src/lib.rs, search for <code>use_lints!</code>. You will see something such as:</p>
<pre><code class="language-rs">use_lints! {
almost_swapped: lints::almost_swapped::AlmostSwappedLint,
divide_by_zero: lints::divide_by_zero::DivideByZeroLint,
empty_if: lints::empty_if::EmptyIfLint,
...
}
</code></pre>
<p>Put your lint in this list (alphabetical order) as the following format:</p>
<pre><code>lint_name: lints::module_name::LintObject,
</code></pre>
<p>For us, this would be:</p>
<pre><code>cool_lint: lints::cool_lint::CoolLint,
</code></pre>
<p>Next, in <code>selene-lib/src/lints.rs</code>, search for <code>pub mod</code>, and you will see:</p>
<pre><code class="language-rs">pub mod almost_swapped;
pub mod divide_by_zero;
pub mod empty_if;
...
</code></pre>
<p>Put your module name in this list, also in alphabetical order.</p>
<pre><code class="language-rs">pub mod almost_swapped;
pub mod cool_lint;
pub mod divide_by_zero;
pub mod empty_if;
...
</code></pre>
<p>And we're done! You should be able to <code>cargo build --bin selene</code> and be able to use your new lint.</p>
<h3 id="writing-tests"><a class="header" href="#writing-tests">Writing tests</a></h3>
<p>The selene codebase uses tests extensively for lints. It means we never have to actually build the CLI tool in order to test, and we can make sure we don't have any regressions. <strong>Testing is required if you want to submit your lint to the selene codebase.</strong></p>
<p>To write a simple test, create a folder in <code>selene-lib/tests</code> with the name of your lint. Then, create as many <code>.lua</code> files as you want to test. These should contain both cases that do and do not lint. For our cases, we're going to assume our test is called <code>cool_lint.lua</code>.</p>
<p>Then, in your lint module, add at the bottom:</p>
<pre><code class="language-rs">#[cfg(test)]
mod tests {
use super::{super::test_util::test_lint, *};
#[test]
fn test_cool_lint() {
test_lint(
CoolLint::new(()).unwrap(),
"cool_lint",
"cool_lint",
);
}
}
</code></pre>
<p>Let's discuss what this code means, assuming you're familiar with <a href="https://doc.rust-lang.org/book/ch11-00-testing.html">the way tests are written and performed in Rust</a>.</p>
<p>The <code>test_lint</code> function is the easiest way to test that a lint works. It'll search the source code we made before, run selene on it (only your lint), and check its results with the existing <code>[filename].stderr</code> file, or create one if it does not yet exist.</p>
<p>The first argument is the lint object to use. <code>CoolLint::new(())</code> just means "create <code>CoolLint</code> with a configuration of <code>()</code>". If your lint specifies a configuration, this will instead be something such as <code>CoolLintConfig::default()</code> or whatever you're specifically testing.</p>
<p>The <code>.unwrap()</code> is just because <code>CoolLint::new</code> returns a <code>Result</code>. If you want to test configuration errors, you can avoid <code>test_lint</code> altogether and just test <code>CoolLint::new(...).is_err()</code> directly.</p>
<p>The first <code>"cool_lint"</code> is the name of the folder we created. The second <code>"cool_lint"</code> is the name of the <em>Lua file</em> we created.</p>
<p>Now, just run <code>cargo test</code>, and a <code>.stderr</code> file will be automatically generated! You can manipulate it however you see fit as well as modifying your lint, and so long as the file is there, selene will make sure that its accurate.</p>
<p>Optionally, you can add a <code>.std.toml</code> with the same name as the test next to the lua file, where you can specify a custom <a href="./usage/std.html">standard library</a> to use. If you do not, the Lua 5.1 standard library will be used.</p>
<h3 id="documenting-it"><a class="header" href="#documenting-it">Documenting it</a></h3>
<p>This step is only if you are contributing to the selene codebase, and not just writing personal lints (though I'm sure your other programmers would love if you did this).</p>
<p>To document a new lint, edit <code>docs/src/SUMMARY.md</code>, and add your lint to the table of contents along the rest. As with everything else, make sure it's in alphabetical order.</p>
<p>Then, edit the markdown file it creates (if you have <code>mdbook serve</code> on, it'll create it for you), and write it in this format:</p>
<pre><code># lint_name
## What it does
Explain what your lint does, simply.
## Why this is bad
Explain why a user would want to lint this.
## Configuration
Specify any configuration if it exists.
## Example
```lua
-- Bad code here
```
...should be written as...
```lua
-- Good code here
```
## Remarks
If there's anything else a user should know when using this lint, write it here.
</code></pre>
<p>This isn't a strict format, and you can mess with it as appropriate. For example, <code>standard_library</code> does not have a "Why this is bad" section as not only is it a very encompassing lint, but it should be fairly obvious. Many lints don't specify a "...should be written as..." as it is either something with various potential fixes (such as <a href="./lints/global_usage.html"><code>global_usage</code></a>) or because the "good code" is just removing parts entirely (such as <a href="./lints/unbalanced_assignments.html"><code>unbalanced_assignments</code></a>).</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="lints"><a class="header" href="#lints">Lints</a></h1>
<p>The following is the list of lints that selene will check for in your code.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="almost_swapped"><a class="header" href="#almost_swapped">almost_swapped</a></h1>
<h2 id="what-it-does"><a class="header" href="#what-it-does">What it does</a></h2>
<p>Checks for <code>foo = bar; bar = foo</code> sequences.</p>
<h2 id="why-this-is-bad"><a class="header" href="#why-this-is-bad">Why this is bad</a></h2>
<p>This looks like a failed attempt to swap.</p>
<h2 id="example"><a class="header" href="#example">Example</a></h2>
<pre><code class="language-lua">a = b
b = a
</code></pre>
<p>...should be written as...</p>
<pre><code class="language-lua">a, b = b, a
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="constant_table_comparison"><a class="header" href="#constant_table_comparison">constant_table_comparison</a></h1>
<h2 id="what-it-does-1"><a class="header" href="#what-it-does-1">What it does</a></h2>
<p>Checks for direct comparisons with constant tables.</p>
<h2 id="why-this-is-bad-1"><a class="header" href="#why-this-is-bad-1">Why this is bad</a></h2>
<p>This will always fail.</p>
<h2 id="example-1"><a class="header" href="#example-1">Example</a></h2>
<pre><code class="language-lua">if x == { "a", "b", "c" } then
</code></pre>
<p>...will never pass.</p>
<pre><code class="language-lua">if x == {} then
</code></pre>
<p>...should be written as...</p>
<pre><code class="language-lua">if next(x) == nil then
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="deprecated-1"><a class="header" href="#deprecated-1">deprecated</a></h1>
<h2 id="what-it-does-2"><a class="header" href="#what-it-does-2">What it does</a></h2>
<p>Checks for use of deprecated fields and functions, as configured <a href="lints/../usage/std.html#deprecated">by your standard library</a>.</p>
<h2 id="why-this-is-bad-2"><a class="header" href="#why-this-is-bad-2">Why this is bad</a></h2>
<p>Deprecated fields may not be getting any support, or even face the possibility of being removed.</p>
<h2 id="configuration-1"><a class="header" href="#configuration-1">Configuration</a></h2>
<p><code>allow</code> - A list of patterns where the deprecated lint will not throw. For instance, <code>["table.getn"]</code> will allow you to use <code>table.getn</code>, even though it is deprecated. This supports wildcards, so <code>table.*</code> will allow both <code>table.getn</code> and <code>table.foreach</code>.</p>
<h2 id="example-2"><a class="header" href="#example-2">Example</a></h2>
<pre><code class="language-lua">local count = table.getn(x)
</code></pre>
<p>...should be written as...</p>
<pre><code class="language-lua">local count = #x
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="divide_by_zero"><a class="header" href="#divide_by_zero">divide_by_zero</a></h1>
<h2 id="what-it-does-3"><a class="header" href="#what-it-does-3">What it does</a></h2>
<p>Checks for division by zero. Allows <code>0 / 0</code> as a way to get <a href="https://en.wikipedia.org/wiki/NaN">nan</a>.</p>
<h2 id="why-this-is-bad-3"><a class="header" href="#why-this-is-bad-3">Why this is bad</a></h2>
<p><code>n / 0</code> equals <code>math.huge</code> when n is positive, and <code>-math.huge</code> when n is negative. Use these values directly instead, as using the <code>/ 0</code> way is confusing to read and non-idiomatic.</p>
<h2 id="example-3"><a class="header" href="#example-3">Example</a></h2>
<pre><code class="language-lua">print(1 / 0)
print(-1 / 0)
</code></pre>
<p>...should be written as...</p>
<pre><code class="language-lua">print(math.huge)
print(-math.huge)
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="duplicate_keys"><a class="header" href="#duplicate_keys">duplicate_keys</a></h1>
<h2 id="what-it-does-4"><a class="header" href="#what-it-does-4">What it does</a></h2>
<p>Checks for duplicate keys being defined inside of tables.</p>
<h2 id="why-this-is-bad-4"><a class="header" href="#why-this-is-bad-4">Why this is bad</a></h2>
<p>Tables with a key defined more than once will only use one of the values.</p>
<h2 id="example-4"><a class="header" href="#example-4">Example</a></h2>
<pre><code class="language-lua">local foo = {
a = 1,
b = 5,
["a"] = 3, -- duplicate definition
c = 3,
b = 1, -- duplicate definition
}
local bar = {
"foo",
"bar",
[1524] = "hello",
"baz",
"foobar",
[2] = "goodbye", -- duplicate to `bar` which has key `2`
}
</code></pre>
<h2 id="remarks"><a class="header" href="#remarks">Remarks</a></h2>
<p>Only handles keys which constant string/number literals or named (such as <code>{ a = true }</code>).
Array-like values are also handled, where <code>{"foo"}</code> is implicitly handled as <code>{ [1] = "foo" }</code>.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="empty_if"><a class="header" href="#empty_if">empty_if</a></h1>
<h2 id="what-it-does-5"><a class="header" href="#what-it-does-5">What it does</a></h2>
<p>Checks for empty if blocks.</p>
<h2 id="why-this-is-bad-5"><a class="header" href="#why-this-is-bad-5">Why this is bad</a></h2>
<p>You most likely forgot to write code in there or commented it out without commenting out the if statement itself.</p>
<h2 id="configuration-2"><a class="header" href="#configuration-2">Configuration</a></h2>
<p><code>comments_count</code> (default: <code>false</code>) - A bool that determines whether or not if statements with exclusively comments are empty.</p>
<h2 id="example-5"><a class="header" href="#example-5">Example</a></h2>
<pre><code class="language-lua">-- Each of these branches count as an empty if.
if a then
elseif b then
else
end
if a then
-- If comments_count is true, this will not count as empty.
end
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="empty_loop"><a class="header" href="#empty_loop">empty_loop</a></h1>
<h2 id="what-it-does-6"><a class="header" href="#what-it-does-6">What it does</a></h2>
<p>Checks for empty loop blocks.</p>
<h2 id="why-this-is-bad-6"><a class="header" href="#why-this-is-bad-6">Why this is bad</a></h2>
<p>You most likely forgot to write code in there or commented it out without commenting out the loop statement itself.</p>
<h2 id="configuration-3"><a class="header" href="#configuration-3">Configuration</a></h2>
<p><code>comments_count</code> (default: <code>false</code>) - A bool that determines whether or not if statements with exclusively comments are empty.</p>
<h2 id="example-6"><a class="header" href="#example-6">Example</a></h2>
<pre><code class="language-lua">-- Counts as an empty loop
for _ in {} do
end
for _ in {} do
-- If comments_count is true, this will not count as empty.
end
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="global_usage"><a class="header" href="#global_usage">global_usage</a></h1>
<h2 id="what-it-does-7"><a class="header" href="#what-it-does-7">What it does</a></h2>
<p>Prohibits use of <code>_G</code>.</p>
<h2 id="why-this-is-bad-7"><a class="header" href="#why-this-is-bad-7">Why this is bad</a></h2>