#!/usr/bin/env python3 """ nullfix.py — dart analyze 驱动的空安全批量修复(保守、带语法损坏自检)。 处理的规则(全部由 analyzer 位置驱动,逐轮迭代): 1. unchecked_use_of_nullable_value -> 在接收者后插 '!'(属性/方法/[]/运算符) 2. not_initialized_non_nullable_instance_field / _variable -> 字段/变量改为可空(在类型后插 '?'),跳过 return/this./关键词行 3. missing_default_value_for_parameter -> 参数改为可空(类型后插 '?'),跳过 field-formal this.x 不处理(需语义判断): argument_type_not_assignable / invalid_assignment (这两类留给后续:多为 nullable 传给非空形参,常伴随 1/2 解决后自动减少) 每轮结束检测 expected_token / missing_identifier(语法损坏),有则中止。 用法: python3 tools/nullfix.py [file] [--max-rounds N] """ import re, subprocess, sys, os ROUNDS = 40 args=[] i=0 while i=0 and line[j]==' ': j-=1 if j<0 or line[j]=='!': continue if line[j].isalnum() or line[j] in '_)?]': return line[:j+1]+'!'+line[j+1:] return None return None def insert_late(line): stripped=line.strip() if not stripped or stripped.startswith('late '): return None head=stripped.split(';')[0] if '?' in head or '=' in head: return None # 已可空/已初始化 indent=line[:len(line)-len(stripped)] if stripped.startswith('static '): return indent+'static late '+stripped[len('static '):] if stripped.startswith('external '): return indent+'external late '+stripped[len('external '):] return indent+'late '+stripped def insert_nullable_before_name(line, name, col0): """把 'Type name' 改为 'Type? name'。跳过 field-formal(this.)、关键词行。""" stripped=line.strip() first=stripped.split(' ')[0] if stripped else '' if first.rstrip(')').rstrip('(') in KEYWORDS: return None idx=find_name_col(line, name, col0) if idx is None: return None # name 前一个非空字符 j=idx-1 while j>=0 and line[j] in ' \t': j-=1 if j<0: return None if line[j]=='.': return None # field-formal this.name if line[j]=='?': return None # 已可空 # j 应是类型末尾(字母/>/)/]) if not (line[j].isalnum() or line[j] in '_>]?)'): return None return line[:j+1]+'?'+line[j+1:] def apply_round(errs): changed=0; files={} def get(path): if path not in files: files[path]=open('lib/'+path).read().split('\n') return files[path] def flush(): for p,ls in files.items(): open('lib/'+p,'w').write('\n'.join(ls)) for path,line,col,msg,rule in errs: if not os.path.exists('lib/'+path): continue L=get(path) if line>len(L): continue old=L[line-1]; new=old if rule=='unchecked_use_of_nullable_value': cls=classify_use(msg) if cls: new=insert_bang(old, col-1, cls[0], cls[1]) elif rule=='missing_default_value_for_parameter': nm=NAME.search(msg) if nm: new=insert_nullable_before_name(old, nm.group(1), col-1) elif rule in ('not_initialized_non_nullable_instance_field','not_initialized_non_nullable_variable'): new=insert_late(old) if new and new!=old: L[line-1]=new; changed+=1 flush() return changed rounds=0 while rounds