1#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
8#![allow(
15 clippy::cast_precision_loss,
16 clippy::cast_possible_truncation,
17 clippy::cast_sign_loss
18)]
19
20use std::fmt;
21
22use crate::checker::Checker;
23use crate::macros::implement_metric_trait;
24
25use crate::*;
26
27#[derive(Clone, Debug, PartialEq)]
37#[non_exhaustive]
38pub struct Stats {
39 tokens: usize,
40 tokens_sum: usize,
41 tokens_min: usize,
42 tokens_max: usize,
43 space_count: usize,
44}
45
46impl Default for Stats {
47 fn default() -> Self {
48 Self {
49 tokens: 0,
50 tokens_sum: 0,
51 tokens_min: usize::MAX,
52 tokens_max: 0,
53 space_count: 1,
54 }
55 }
56}
57
58impl fmt::Display for Stats {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 write!(
61 f,
62 "tokens: {}, \
63 tokens_average: {}, \
64 tokens_min: {}, \
65 tokens_max: {}",
66 self.tokens_sum(),
67 self.tokens_average(),
68 self.tokens_min(),
69 self.tokens_max(),
70 )
71 }
72}
73
74impl Stats {
75 pub fn merge(&mut self, other: &Stats) {
77 self.tokens_min = self.tokens_min.min(other.tokens_min);
78 self.tokens_max = self.tokens_max.max(other.tokens_max);
79 self.tokens_sum += other.tokens_sum;
80 self.space_count += other.space_count;
81 }
82
83 #[inline]
85 #[must_use]
86 pub fn tokens_sum(&self) -> u64 {
87 self.tokens_sum as u64
88 }
89
90 #[inline]
92 #[must_use]
93 pub fn tokens_average(&self) -> f64 {
94 crate::metrics::average(self.tokens_sum() as f64, self.space_count)
95 }
96
97 #[inline]
105 #[must_use]
106 pub fn tokens_min(&self) -> u64 {
107 if self.tokens_min == usize::MAX {
108 0
109 } else {
110 self.tokens_min as u64
111 }
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn tokens_max(&self) -> u64 {
118 self.tokens_max as u64
119 }
120
121 #[inline]
122 pub(crate) fn compute_sum(&mut self) {
123 self.tokens_sum += self.tokens;
124 }
125
126 #[inline]
127 pub(crate) fn compute_minmax(&mut self) {
128 self.tokens_min = self.tokens_min.min(self.tokens);
129 self.tokens_max = self.tokens_max.max(self.tokens);
130 self.compute_sum();
131 }
132}
133
134#[doc(hidden)]
135pub(crate) trait Tokens
137where
138 Self: Checker,
139{
140 fn compute(node: &Node, stats: &mut Stats) {
143 if node.child_count() != 0 {
144 return;
145 }
146 let in_comment =
152 std::iter::successors(Some(*node), Node::parent).any(|n| Self::is_comment(&n));
153 if !in_comment {
154 stats.tokens += 1;
155 }
156 }
157}
158
159implement_metric_trait!(
160 [Tokens],
161 PythonCode,
162 MozjsCode,
163 JavascriptCode,
164 TypescriptCode,
165 TsxCode,
166 CppCode,
167 MozcppCode,
168 CCode,
169 ObjcCode,
170 RustCode,
171 PreprocCode,
172 CcommentCode,
173 JavaCode,
174 KotlinCode,
175 GoCode,
176 PerlCode,
177 BashCode,
178 LuaCode,
179 TclCode,
180 PhpCode,
181 CsharpCode,
182 ElixirCode,
183 RubyCode,
184 GroovyCode,
185 IrulesCode
186);
187
188#[cfg(test)]
189#[allow(
190 clippy::float_cmp,
191 clippy::cast_precision_loss,
192 clippy::cast_possible_truncation,
193 clippy::cast_sign_loss,
194 clippy::similar_names,
195 clippy::doc_markdown,
196 clippy::needless_raw_string_hashes,
197 clippy::too_many_lines
198)]
199mod tests {
200 use crate::tools::check_metrics;
201
202 use super::*;
203
204 #[test]
207 fn python_tokens_exact_count() {
208 check_metrics::<PythonParser>("def foo(x): return x", "foo.py", |metric| {
209 assert_eq!(metric.tokens.tokens_sum(), 8);
210 assert!(metric.tokens.tokens_max() >= 7);
211 });
212 }
213
214 #[test]
216 fn python_tokens_comments_excluded() {
217 check_metrics::<PythonParser>(
218 "def foo(x): return x # explanation\n# header\n",
219 "foo.py",
220 |metric| {
221 assert_eq!(metric.tokens.tokens_sum(), 8);
222 },
223 );
224 }
225
226 #[test]
228 fn python_tokens_whitespace_excluded() {
229 check_metrics::<PythonParser>(
230 "\n\n def foo(x):\n return x\n\n",
231 "foo.py",
232 |metric| {
233 assert_eq!(metric.tokens.tokens_sum(), 8);
234 },
235 );
236 }
237
238 #[test]
242 fn python_tokens_distinct_from_halstead() {
243 check_metrics::<PythonParser>("def foo(x): return (x + 1)", "foo.py", |metric| {
244 let halstead_total =
245 metric.halstead.total_operators() + metric.halstead.total_operands();
246 assert!(
247 metric.tokens.tokens_sum() > halstead_total,
248 "expected tokens ({}) > halstead N1+N2 ({}); punctuation \
249 like `(`, `)`, `:` should contribute to tokens but not Halstead",
250 metric.tokens.tokens_sum(),
251 halstead_total,
252 );
253 });
254 }
255
256 #[test]
264 fn python_tokens_nested_attribution() {
265 check_metrics::<PythonParser>(
266 "def outer():\n def inner():\n return 1\n",
267 "foo.py",
268 |metric| {
269 assert_eq!(metric.tokens.tokens_sum(), 12);
270 assert_eq!(metric.tokens.tokens_max(), 7);
271 assert_eq!(metric.tokens.tokens_min(), 0);
272 },
273 );
274 }
275
276 #[test]
279 fn cpp_tokens_block_comments_excluded() {
280 check_metrics::<CppParser>(
281 "int foo(int x) { /* multi\n line */ return x; }",
282 "foo.cpp",
283 |m| {
284 assert_eq!(m.tokens.tokens_sum(), 11);
287 },
288 );
289 check_metrics::<CppParser>("int foo(int x) { return x; }", "foo.cpp", |m| {
290 assert_eq!(m.tokens.tokens_sum(), 11);
291 });
292 }
293
294 #[test]
298 fn cpp_tokens_line_comments_excluded() {
299 check_metrics::<CppParser>("int x = 1; // a one-line comment\n", "foo.cpp", |m| {
300 assert_eq!(m.tokens.tokens_sum(), 5);
301 });
302 check_metrics::<CppParser>("int x = 1;\n", "foo.cpp", |m| {
303 assert_eq!(m.tokens.tokens_sum(), 5);
304 });
305 }
306
307 #[test]
310 fn cpp_tokens_whitespace_excluded() {
311 check_metrics::<CppParser>("\n\nint foo(int x) {\n return x;\n}\n", "foo.cpp", |m| {
312 assert_eq!(m.tokens.tokens_sum(), 11);
314 });
315 }
316
317 #[test]
322 fn cpp_tokens_distinct_from_halstead() {
323 check_metrics::<CppParser>("int foo(int x) { return (x + 1); }", "foo.cpp", |m| {
324 let halstead_total = m.halstead.total_operators() + m.halstead.total_operands();
325 assert!(
326 m.tokens.tokens_sum() > halstead_total,
327 "expected tokens ({}) > halstead N1+N2 ({}); punctuation like \
328 `(`, `)`, `{{`, `}}` and `;` should contribute to tokens but not Halstead",
329 m.tokens.tokens_sum(),
330 halstead_total,
331 );
332 });
333 }
334
335 #[test]
346 fn cpp_tokens_nested_attribution() {
347 check_metrics::<CppParser>(
348 "struct S {\n int method(int a) { return a + 1; }\n};\nint outer() { return 2; }\n",
349 "foo.cpp",
350 |m| {
351 assert_eq!(m.tokens.tokens_sum(), 27);
352 assert_eq!(m.tokens.tokens_max(), 13);
353 assert_eq!(m.tokens.tokens_min(), 1);
354 },
355 );
356 }
357
358 #[test]
360 fn java_tokens_line_comments_excluded() {
361 check_metrics::<JavaParser>(
362 "class A { void foo() { // hi\n return; } }",
363 "A.java",
364 |m| {
365 assert_eq!(m.tokens.tokens_sum(), 12);
367 },
368 );
369 check_metrics::<JavaParser>("class A { void foo() { return; } }", "A.java", |m| {
370 assert_eq!(m.tokens.tokens_sum(), 12);
371 });
372 }
373
374 #[test]
375 fn groovy_tokens_line_comments_excluded() {
376 check_metrics::<GroovyParser>(
378 "class A { void foo() { // hi\n return\n } }",
379 "A.groovy",
380 |m| {
381 assert_eq!(m.tokens.tokens_sum(), 11);
385 },
386 );
387 }
388
389 #[test]
393 fn javascript_tokens_html_comment_excluded() {
394 check_metrics::<JavascriptParser>("<!-- hi -->\nlet x = 1;\n", "foo.js", |m| {
395 assert_eq!(m.tokens.tokens_sum(), 5);
397 });
398 check_metrics::<JavascriptParser>("let x = 1;\n", "foo.js", |m| {
399 assert_eq!(m.tokens.tokens_sum(), 5);
400 });
401 }
402
403 #[test]
407 fn groovy_tokens_groovydoc_excluded() {
408 check_metrics::<GroovyParser>(
409 "/** doc */\nclass A { void f() { return\n } }\n",
410 "A.groovy",
411 |m| {
412 assert_eq!(m.tokens.tokens_sum(), 11);
415 },
416 );
417 check_metrics::<GroovyParser>("class A { void f() { return\n } }\n", "A.groovy", |m| {
418 assert_eq!(m.tokens.tokens_sum(), 11);
419 });
420 }
421
422 #[test]
425 fn rust_tokens_doc_comments_excluded() {
426 check_metrics::<RustParser>(
427 "/// outer doc\n/// more doc\nfn f() { let x = 1; }",
428 "foo.rs",
429 |m| {
430 assert_eq!(m.tokens.tokens_sum(), 11);
432 },
433 );
434 check_metrics::<RustParser>("fn f() { let x = 1; }", "foo.rs", |m| {
435 assert_eq!(m.tokens.tokens_sum(), 11);
436 });
437 }
438
439 #[test]
448 fn smoke_python() {
449 check_metrics::<PythonParser>("x = 1\n", "foo.py", |m| {
450 assert!(m.tokens.tokens_sum() > 0);
451 });
452 }
453
454 #[test]
455 fn smoke_rust() {
456 check_metrics::<RustParser>("fn f() { let x = 1; }", "foo.rs", |m| {
457 assert!(m.tokens.tokens_sum() > 0);
458 });
459 }
460
461 #[test]
462 fn smoke_cpp() {
463 check_metrics::<CppParser>("int x = 1;", "foo.cpp", |m| {
464 assert!(m.tokens.tokens_sum() > 0);
465 });
466 }
467
468 #[test]
469 fn smoke_java() {
470 check_metrics::<JavaParser>("class A { int x = 1; }", "A.java", |m| {
471 assert!(m.tokens.tokens_sum() > 0);
472 });
473 }
474
475 #[test]
476 fn smoke_csharp() {
477 check_metrics::<CsharpParser>("class A { int X = 1; }", "A.cs", |m| {
478 assert!(m.tokens.tokens_sum() > 0);
479 });
480 }
481
482 #[test]
483 fn smoke_javascript() {
484 check_metrics::<JavascriptParser>("let x = 1;", "foo.js", |m| {
485 assert!(m.tokens.tokens_sum() > 0);
486 });
487 }
488
489 #[test]
490 fn smoke_mozjs() {
491 check_metrics::<MozjsParser>("let x = 1;", "foo.js", |m| {
492 assert!(m.tokens.tokens_sum() > 0);
493 });
494 }
495
496 #[test]
497 fn smoke_typescript() {
498 check_metrics::<TypescriptParser>("const x: number = 1;", "foo.ts", |m| {
499 assert!(m.tokens.tokens_sum() > 0);
500 });
501 }
502
503 #[test]
504 fn smoke_tsx() {
505 check_metrics::<TsxParser>("const x: number = 1;", "foo.tsx", |m| {
506 assert!(m.tokens.tokens_sum() > 0);
507 });
508 }
509
510 #[test]
511 fn smoke_go() {
512 check_metrics::<GoParser>("package main\nfunc f() {}", "foo.go", |m| {
513 assert!(m.tokens.tokens_sum() > 0);
514 });
515 }
516
517 #[test]
518 fn smoke_kotlin() {
519 check_metrics::<KotlinParser>("fun f(): Int = 1", "foo.kt", |m| {
520 assert!(m.tokens.tokens_sum() > 0);
521 });
522 }
523
524 #[test]
525 fn smoke_lua() {
526 check_metrics::<LuaParser>("local x = 1", "foo.lua", |m| {
527 assert!(m.tokens.tokens_sum() > 0);
528 });
529 }
530
531 #[test]
532 fn smoke_bash() {
533 check_metrics::<BashParser>("x=1", "foo.sh", |m| {
534 assert!(m.tokens.tokens_sum() > 0);
535 });
536 }
537
538 #[test]
539 fn smoke_tcl() {
540 check_metrics::<TclParser>("set x 1", "foo.tcl", |m| {
541 assert!(m.tokens.tokens_sum() > 0);
542 });
543 }
544
545 #[test]
546 fn smoke_perl() {
547 check_metrics::<PerlParser>("my $x = 1;", "foo.pl", |m| {
548 assert!(m.tokens.tokens_sum() > 0);
549 });
550 }
551
552 #[test]
553 fn smoke_php() {
554 check_metrics::<PhpParser>("<?php $x = 1;", "foo.php", |m| {
555 assert!(m.tokens.tokens_sum() > 0);
556 });
557 }
558
559 #[test]
560 fn smoke_preproc() {
561 check_metrics::<PreprocParser>("#define FOO 1\n", "foo.h", |m| {
562 assert!(m.tokens.tokens_sum() > 0);
563 });
564 }
565
566 #[test]
567 fn smoke_ccomment() {
568 check_metrics::<CcommentParser>("int x = 1;", "foo.c", |m| {
571 assert!(m.tokens.tokens_sum() > 0);
572 });
573 }
574
575 #[test]
576 fn smoke_c() {
577 check_metrics::<CParser>("int x = 1;\n", "foo.c", |m| {
578 assert!(m.tokens.tokens_sum() > 0);
579 });
580 }
581
582 #[test]
583 fn smoke_objc() {
584 check_metrics::<ObjcParser>("int x = 1;\n", "foo.m", |m| {
585 assert!(m.tokens.tokens_sum() > 0);
586 });
587 }
588
589 #[test]
590 fn smoke_elixir() {
591 check_metrics::<ElixirParser>("defmodule Foo do\n :ok\nend\n", "foo.ex", |m| {
592 assert!(m.tokens.tokens_sum() > 0);
593 });
594 }
595
596 #[test]
597 fn smoke_ruby() {
598 check_metrics::<RubyParser>("def foo\n a = 1\nend\n", "foo.rb", |m| {
599 assert!(m.tokens.tokens_sum() > 0);
600 });
601 }
602
603 #[test]
604 fn smoke_irules() {
605 check_metrics::<IrulesParser>("when X {\n set x 1\n}\n", "foo.irule", |m| {
606 assert!(m.tokens.tokens_sum() > 0);
607 });
608 }
609}