Skip to content

Commit ff7484a

Browse files
committed
add script used
1 parent 7fdc162 commit ff7484a

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import re
2+
import os
3+
from typing import List, Tuple
4+
import argparse
5+
6+
# This is a poor man's check for null dereference check for tsx files
7+
# Use it like this
8+
9+
# python .github/scripts/typescript-null-checker.py --format text ./datahub-web-react/src --fix
10+
# cd datahub-web-react
11+
# yarn install
12+
# yarn lint-fix
13+
14+
### Manually fix any problems created by this script (3-4 problems got created) and re-run the above steps
15+
16+
class TypeScriptNullChecker:
17+
def __init__(self):
18+
# Pattern to match chains that already have at least one optional chaining operator
19+
self.chain_pattern = r'([a-zA-Z_][a-zA-Z0-9_]*(?:\?\.|\.)(?:[a-zA-Z_][a-zA-Z0-9_]*(?:\?\.|\.))*[a-zA-Z_][a-zA-Z0-9_]*(?:\[\d+\](?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)*)'
20+
21+
def find_property_chains(self, code: str) -> List[str]:
22+
"""Find property chains that have at least one optional chaining operator but might be missing others."""
23+
chains = re.findall(self.chain_pattern, code)
24+
return [
25+
chain for chain in chains
26+
if '?.' in chain # Must have at least one optional chaining
27+
and ('.' in chain.replace('?.', '')) # Must have at least one regular dot
28+
]
29+
30+
def analyze_chain(self, chain: str) -> Tuple[bool, str]:
31+
"""Analyze a property chain that already has some optional chaining for missing operators."""
32+
parts = re.split(r'[\.\[\]]', chain)
33+
indices = [i for i, char in enumerate(chain) if char in '.[]']
34+
35+
has_issue = False
36+
fixed_chain = list(chain)
37+
last_optional = -1
38+
39+
for i, char in enumerate(chain):
40+
if char == '?':
41+
last_optional = i
42+
43+
for i in indices:
44+
if i <= last_optional:
45+
continue
46+
47+
if chain[i] == '.' and (i == 0 or chain[i-1] != '?'):
48+
if i > 0 and chain[i-1] != ']':
49+
has_issue = True
50+
fixed_chain.insert(i, '?')
51+
indices = [idx + 1 if idx > i else idx for idx in indices]
52+
53+
elif chain[i] == '[':
54+
next_dot = next((idx for idx in indices if idx > i and chain[idx] == '.'), None)
55+
if next_dot and (next_dot == 0 or chain[next_dot-1] != '?'):
56+
has_issue = True
57+
fixed_chain.insert(next_dot, '?')
58+
indices = [idx + 1 if idx > next_dot else idx for idx in indices]
59+
60+
return has_issue, ''.join(fixed_chain)
61+
62+
def check_code(self, code: str, filename: str = "") -> List[dict]:
63+
"""Check TypeScript code for potential null dereference issues."""
64+
issues = []
65+
chains = self.find_property_chains(code)
66+
67+
for chain in chains:
68+
has_issue, fixed_chain = self.analyze_chain(chain)
69+
if has_issue:
70+
issues.append({
71+
'filename': filename,
72+
'original': chain,
73+
'fixed': fixed_chain,
74+
'message': f'Inconsistent null checking detected in chain: {chain}'
75+
})
76+
77+
return issues
78+
79+
def scan_directory(directory: str, fix: bool = False) -> List[dict]:
80+
"""Recursively scan a directory for TypeScript files and check for null dereference issues."""
81+
checker = TypeScriptNullChecker()
82+
all_issues = []
83+
84+
for root, _, files in os.walk(directory):
85+
for file in files:
86+
if file.endswith(('.ts', '.tsx')):
87+
file_path = os.path.join(root, file)
88+
try:
89+
with open(file_path, 'r', encoding='utf-8') as f:
90+
content = f.read()
91+
92+
issues = checker.check_code(content, file_path)
93+
if issues:
94+
all_issues.extend(issues)
95+
96+
if fix:
97+
# Apply fixes directly
98+
fixed_content = content
99+
for issue in issues:
100+
fixed_content = fixed_content.replace(issue['original'], issue['fixed'])
101+
102+
# Write fixed content back to file
103+
with open(file_path, 'w', encoding='utf-8') as f:
104+
f.write(fixed_content)
105+
106+
except Exception as e:
107+
print(f"Error processing {file_path}: {str(e)}")
108+
109+
return all_issues
110+
111+
def main():
112+
parser = argparse.ArgumentParser(description='Detect inconsistent null checking in TypeScript files')
113+
parser.add_argument('directory', help='Directory to scan')
114+
parser.add_argument('--fix', action='store_true', help='Automatically fix issues')
115+
parser.add_argument('--format', choices=['text', 'json'], default='text', help='Output format')
116+
args = parser.parse_args()
117+
118+
issues = scan_directory(args.directory, args.fix)
119+
120+
if args.format == 'json':
121+
import json
122+
print(json.dumps(issues, indent=2))
123+
else:
124+
if not issues:
125+
print("No inconsistent null checking found.")
126+
else:
127+
print(f"\nFound {len(issues)} instances of inconsistent null checking:")
128+
for issue in issues:
129+
print(f"\nFile: {issue['filename']}")
130+
print(f"Original: {issue['original']}")
131+
print(f"Fixed to: {issue['fixed']}")
132+
133+
if args.fix:
134+
print(f"\nApplied fixes to {len(issues)} locations.")
135+
136+
if __name__ == "__main__":
137+
main()

0 commit comments

Comments
 (0)