-
Notifications
You must be signed in to change notification settings - Fork 0
/
ts_tagger
executable file
·127 lines (109 loc) · 5.09 KB
/
ts_tagger
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
#!/usr/bin/env perl
# what this does:
# * given a list of .ts (Typescript) files on stdin
# * output basic tags to stdout
# * in format matching `Universal Ctags` when run with options:
# --tag-relative=yes --excmd=number --fields=+K+z+S+l+n --extras=+f
#
# why it exists:
# Because current (20240901) HEAD of `Universal Ctags`, when scanning .ts
# (Typescript) files *that build*, often "loses its mind" and fails to generate
# even the most basic tags (e.g. for class methods), which is an *utterly
# crippling* *functional failure* that renders me less effective in my work
# (Node TS). Since I cannot possibly fix whatever is lacking in the `Universal
# Ctags` Typescript language parser, which has apparently been in existence for
# some years, but isn't being fixed (see its github Issues, where there are
# various pleas for Typescript (among others) parsing experts to assist), and
# since my attempts to modify/augment ctags regex parameterization were leading
# to naught but insanity, I thought (thinking back to those "ctags regex
# parameterization" attempts and how they *might* work if used in isolation)
# "how about I just add-in the tags that, if certain assumptions about code
# formatting are made, should be easy to generate via line-based parsing of the
# source file?" Of course, the devil is in the words "should be". But it turns
# out (so far) to be feasible.
#
# integration:
# see script `tagts`
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use Cwd;
my $cwd = getcwd();
my $debug = $ENV{DEBUG} // 0;
my $class_pattern = qr/^(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/;
my $method_pattern = qr/^
\s*(?:public|private|protected)?\s* # Optional access modifier
(?:static\s+)? # Optional static keyword
(?:async\s+)? # Optional async keyword
(?!function\b) # Negative lookahead for 'function' keyword
(\w+) # Method name
(?:<[^>]+>)? # Optional generic type parameters
\s*\([^)]*\)\s* # Parameters
(?::\s*[^{;]+)? # Optional return type
\s*{ # Opening brace
/x;
my $function_pattern = qr/^
\s*(?:export\s+)? # Optional export keyword
(?:async\s+)? # Optional async keyword
function\s+ # 'function' keyword
(\w+) # Function name
(?:<[^>]+>)? # Optional generic type parameters
\s*\([^)]*\)\s* # Parameters
(?::\s*[^{;]+)? # Optional return type
\s*{ # Opening brace
/x;
# List of reserved keywords and common function calls to exclude
my %exclude_words = map { $_ => 1 } qw(
break case catch class const continue debugger default delete do else
enum export extends false finally for if import in instanceof
new null return super switch this throw true try typeof var void while with
as implements interface let package private protected public static yield
any boolean constructor declare get module require set symbol type from of
assert console log warn error resolve reject
);
my $tag_count = 0;
sub output_tag {
my ($name, $file, $line, $kind, $extra) = @_;
if ($debug) {
printf STDERR "DEBUG: %s | %s | %d | %s | %s\n", $name, $file, $line, $kind, $extra // '';
} else {
printf "%s\t%s\t%d;\"\tkind:%s\tline:%d\tlanguage:TypeScript%s\n",
$name, $file, $line, $kind, $line, $extra ? "\t$extra" : '';
}
$tag_count++;
}
while (my $file = <STDIN>) {
chomp $file;
next unless -f $file; # Skip if not a regular file
my $relative_path = File::Spec->abs2rel($file, $cwd);
open(my $in_fh, '<', $file) or die "Could not open file '$file' $!";
my $current_class = "";
my $line_number = 0;
while (my $line = <$in_fh>) {
chomp $line;
$line_number++;
# Skip lines that are likely to be within comments
next if $line =~ m{^\s*//} or $line =~ m{^\s*/\*} or $line =~ m{\*/\s*$};
if ($line =~ $class_pattern) {
$current_class = $1;
my $extends = $2 ? "inherits:$2" : "";
output_tag($current_class, $relative_path, $line_number, 'class', $extends);
}
if ($current_class && $line =~ $method_pattern) {
my $method_name = $1;
# Only add the tag if the method name is not in the exclude list
unless ($exclude_words{$method_name}) {
output_tag($method_name, $relative_path, $line_number, 'method', "class:$current_class");
}
}
elsif ($line =~ $function_pattern) {
my $function_name = $1;
# Only add the tag if the function name is not in the exclude list
unless ($exclude_words{$function_name}) {
output_tag($function_name, $relative_path, $line_number, 'function');
}
}
}
close($in_fh);
}