converting-ts-to-dart — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited converting-ts-to-dart (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Provide clear, idiomatic mappings from TypeScript syntax and patterns to modern Dart 3 equivalents. Every conversion should produce code that feels native to Dart — not a line-by-line transliteration.
#### 1.1 Primitive Types TypeScript and Dart share similar primitives but with different names.
// TypeScript
let name: string = "Zack";
let age: number = 25;
let active: boolean = true;
let items: string[] = ["a", "b"];
let pair: [string, number] = ["age", 25];// Dart 3
final name = 'Zack';
final age = 25;
final active = true;
final items = ['a', 'b'];
final pair = ('age', 25); // Record instead of TupleKey mappings:
| TypeScript | Dart |
|---|---|
string | String |
number | int / double / num |
boolean | bool |
any | dynamic (avoid when possible) |
unknown | Object? |
void | void |
null / undefined | null (Dart has no undefined) |
never | Never |
T[] / Array<T> | List<T> |
[A, B] (Tuple) | (A, B) (Record) |
Record<K, V> / { [key: string]: V } | Map<K, V> |
Set<T> | Set<T> |
Promise<T> | Future<T> |
Observable<T> | Stream<T> |
#### 1.2 Union Types Dart has no direct union type. Convert using sealed classes for complex cases, or Object + pattern matching for simple ones.
// TypeScript
type Result = Success | Failure;
type StringOrNum = string | number;// Dart 3 — sealed class for ADTs
sealed class Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String error; Failure(this.error); }
// Simple union — use pattern matching
void process(Object value) {
switch (value) {
case String s: print('String: $s');
case int n: print('Int: $n');
}
}#### 1.3 Literal Types and Enums
// TypeScript
type Direction = "up" | "down" | "left" | "right";
enum Status { Active = "ACTIVE", Inactive = "INACTIVE" }// Dart 3 — enhanced enum
enum Direction { up, down, left, right }
enum Status {
active('ACTIVE'),
inactive('INACTIVE');
final String value;
const Status(this.value);
}#### 2.1 Interface → Class Dart has no interface keyword. Every class implicitly defines an interface.
// TypeScript
interface User {
readonly id: string;
name: string;
email?: string;
greet(): string;
}// Dart 3 — abstract interface class
abstract interface class User {
String get id;
String get name;
set name(String value);
String? get email;
String greet();
}
// Or as a concrete data class (more common)
class User {
final String id;
String name;
final String? email;
User({required this.id, required this.name, this.email});
String greet() => 'Hello, $name';
}#### 2.2 Type Alias → typedef / Record
// TypeScript
type Point = { x: number; y: number };
type Callback = (data: string) => void;// Dart 3
typedef Point = ({double x, double y});
typedef Callback = void Function(String data);#### 2.3 Generic Constraints
// TypeScript
function merge<T extends object>(a: T, b: Partial<T>): T { ... }// Dart 3
T merge<T extends Object>(T a, T b) { ... }#### 3.1 Constructor Shorthand Dart constructors are more concise than TypeScript's.
// TypeScript
class User {
readonly id: string;
name: string;
constructor(id: string, name: string) {
this.id = id;
this.name = name;
}
}// Dart 3 — initializing formals
class User {
final String id;
String name;
User(this.id, this.name);
}#### 3.2 Access Modifiers TypeScript uses private/protected/public. Dart uses _ prefix for library-private.
| TypeScript | Dart |
|---|---|
public | (default, no prefix) |
private | _ prefix |
protected | _ prefix (no true protected) |
readonly | final |
static | static |
abstract | abstract |
#### 3.3 Inheritance Patterns
// TypeScript
abstract class Shape {
abstract area(): number;
}
class Circle extends Shape {
constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
}// Dart 3
abstract class Shape {
double area();
}
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
double area() => pi * radius * radius; // import 'dart:math';
}#### 4.1 Function Syntax
// TypeScript
const greet = (name: string, age?: number): string => {
return age != null ? `${name} is ${age}` : name;
};// Dart 3
String greet(String name, [int? age]) =>
age != null ? '$name is $age' : name;#### 4.2 Named vs Positional Parameters TypeScript object destructuring → Dart named parameters.
// TypeScript
function createUser({ name, age = 18 }: { name: string; age?: number }) { ... }
createUser({ name: "Zack" });// Dart 3
void createUser({required String name, int age = 18}) { ... }
createUser(name: 'Zack');#### 4.3 Async / Await Nearly identical, but Promise → Future, Promise.all → Future.wait.
// TypeScript
async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
return res.json();
}// Dart 3
Future<List<User>> fetchUsers() async {
final res = await http.get(Uri.parse('/api/users'));
return (jsonDecode(res.body) as List).map(User.fromJson).toList();
}#### 4.4 Stream (Observable equivalent)
// TypeScript (RxJS)
observable$.pipe(map(x => x * 2), filter(x => x > 5)).subscribe(console.log);// Dart 3
stream.map((x) => x * 2).where((x) => x > 5).listen(print);Both languages support strict null checking, with similar operators.
| TypeScript | Dart | Purpose |
|---|---|---|
x?.prop | x?.prop | Optional chaining |
x ?? y | x ?? y | Null coalescing |
x! | x! | Non-null assertion |
x?.prop ?? default | x?.prop ?? default | Chaining + fallback |
x ??= y | x ??= y | Null-aware assignment |
Key difference: Dart's null safety is sound — the compiler guarantees a non-nullable variable is never null at runtime. Prefer type promotion (if (x != null)) over the bang operator !.
#### 6.1 Array → List
// TypeScript
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
const evens = nums.filter(n => n % 2 === 0);
const sum = nums.reduce((a, b) => a + b, 0);
const hasNeg = nums.some(n => n < 0);// Dart 3
final nums = [1, 2, 3];
final doubled = nums.map((n) => n * 2).toList();
final evens = nums.where((n) => n % 2 == 0).toList();
final sum = nums.fold(0, (a, b) => a + b);
final hasNeg = nums.any((n) => n < 0);Method mapping:
| TypeScript | Dart |
|---|---|
.map() | .map().toList() |
.filter() | .where().toList() |
.reduce() | .fold() (with initial value) / .reduce() |
.some() | .any() |
.every() | .every() |
.find() | .firstWhere() |
.findIndex() | .indexWhere() |
.includes() | .contains() |
.flat() | .expand((e) => e).toList() |
.push() | .add() |
.splice() | .removeRange() / .insertAll() |
[...a, ...b] | [...a, ...b] |
#### 6.2 Object → Map
// TypeScript
const config: Record<string, number> = { timeout: 30, retries: 3 };
Object.entries(config).forEach(([k, v]) => console.log(k, v));// Dart 3
final config = <String, int>{'timeout': 30, 'retries': 3};
config.forEach((k, v) => print('$k $v'));Dart 3 pattern matching is more powerful than TypeScript's type narrowing. Lean into it during conversion.
// TypeScript — type narrowing
function describe(shape: Shape) {
if (shape instanceof Circle) {
return `Circle r=${shape.radius}`;
} else if (shape instanceof Rect) {
return `Rect ${shape.w}x${shape.h}`;
}
return "Unknown";
}// Dart 3 — exhaustive switch expression
String describe(Shape shape) => switch (shape) {
Circle(:final radius) => 'Circle r=$radius',
Rect(:final w, :final h) => 'Rect ${w}x$h',
};| TypeScript | Dart |
|---|---|
import { X } from './x' | import 'x.dart' (imports all public) |
import * as lib from './x' | import 'x.dart' as lib |
import { X as Y } from './x' | import 'x.dart' show X (rename not needed often) |
export { X } | Top-level public declarations are auto-exported |
export default X | No equivalent; just import the library |
import type { X } | Not needed; Dart imports are already type-aware |
// TypeScript
try {
await riskyOp();
} catch (e: unknown) {
if (e instanceof HttpError) { ... }
throw e;
}// Dart 3
try {
await riskyOp();
} on HttpError catch (e) {
// handle
} catch (e, stackTrace) {
rethrow;
}| TypeScript Pattern | Dart 3 Equivalent |
|---|---|
JSON.stringify(obj) | jsonEncode(obj) |
JSON.parse(str) | jsonDecode(str) |
console.log(x) | print(x) / debugPrint(x) |
typeof x === 'string' | x is String |
x instanceof Foo | x is Foo |
setTimeout(fn, ms) | Future.delayed(Duration(milliseconds: ms), fn) |
setInterval(fn, ms) | Timer.periodic(Duration(milliseconds: ms), (_) => fn()) |
Date.now() | DateTime.now() |
Math.max(a, b) | max(a, b) (from dart:math) |
Object.keys(obj) | map.keys.toList() |
Array.from({length: n}, (_, i) => i) | List.generate(n, (i) => i) |
str.startsWith('x') | str.startsWith('x') |
str.split(',') | str.split(',') |
` Hello ${name} ` | 'Hello $name' |
final over var when the variable is not reassigned.sealed classes for union type / ADT conversions — never fall back to raw Object + casting when the domain is bounded.switch expressions with destructuring over if-else chains for type narrowing.lowerCamelCase / UpperCamelCase rules.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.