diff --git a/AGENTS.md b/AGENTS.md index 9c015aa..c699b6a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,8 +36,8 @@ If `lat search` fails because no API key is configured, explain to the user that - **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`). - **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`. -- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist. -- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts +- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C/Dart files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct), `[[src/app.dart#Greeter#greet]]` (Dart method). `lat check` validates these exist. +- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C/Dart) or `# @lat: [[section-id]]` (Python) — ties source code to concepts # Test specs diff --git a/CLAUDE.md b/CLAUDE.md index 9c015aa..c699b6a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,8 +36,8 @@ If `lat search` fails because no API key is configured, explain to the user that - **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`). - **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`. -- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist. -- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts +- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C/Dart files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct), `[[src/app.dart#Greeter#greet]]` (Dart method). `lat check` validates these exist. +- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C/Dart) or `# @lat: [[section-id]]` (Python) — ties source code to concepts # Test specs diff --git a/lat.md/markdown.md b/lat.md/markdown.md index bdb21b1..0113833 100644 --- a/lat.md/markdown.md +++ b/lat.md/markdown.md @@ -32,7 +32,7 @@ Resolution is handled by [[src/lattice.ts#resolveRef]]. See [[parser#Short Ref R ### Source Code Links -Wiki links can reference symbols in TypeScript, JavaScript, Python, Rust, Go, and C source files: +Wiki links can reference symbols in TypeScript, JavaScript, Python, Rust, Go, C, and Dart source files: - **`[[src/config.ts#getConfigDir]]`** — the `getConfigDir` function in `src/config.ts` - **`[[src/server.ts#App#listen]]`** — the `listen` method on class `App` in `src/server.ts` @@ -40,9 +40,10 @@ Wiki links can reference symbols in TypeScript, JavaScript, Python, Rust, Go, an - **`[[src/app.go#Greeter#Greet]]`** — the `Greet` method on type `Greeter` in Go - **`[[src/app.h#Greeter]]`** — the `Greeter` struct in a C header - **`[[src/app.h#Greeter#prefix]]`** — the `prefix` field of struct `Greeter` in C +- **`[[src/app.dart#Greeter#greet]]`** — the `greet` method on class `Greeter` in Dart - **`[[src/config.ts]]`** — link to the file itself (no symbol) -Supported extensions: `.ts`, `.tsx`, `.js`, `.jsx`, `.py`, `.rs`, `.go`, `.c`, `.h`. +Supported extensions: `.ts`, `.tsx`, `.js`, `.jsx`, `.py`, `.rs`, `.go`, `.c`, `.h`, `.dart`. Python symbols: functions, classes, methods, module-level variables. Decorated definitions (`@decorator`) are unwrapped transparently — `[[file.py#my_func]]` resolves whether or not `my_func` has decorators, and `# @lat:` comments placed between decorators and the `def`/`class` line are scanned normally. @@ -52,6 +53,8 @@ Go symbols: functions, types (structs, interfaces, type aliases), methods (with C symbols: functions (including pointer-returning like `char *func()`), structs, struct fields/members, enums, enum values (including anonymous enums and `typedef enum` members), typedefs, `#define` macros (both object-like and function-like), variables (including arrays). Struct fields are resolved via the parent struct — `[[file.h#Struct#field]]` matches any `field_declaration` inside `struct Struct { ... }`, including fields nested inside anonymous unions and structs. Enum values can be referenced standalone (`[[file.h#GREEN]]`) or qualified by their enum name (`[[file.h#Color#GREEN]]`); both forms work for named enums, `typedef enum`, and named `typedef enum`. Both `.c` and `.h` files are supported — include guards (`#ifndef`/`#endif`) are walked through transparently. +Dart symbols: functions, classes, methods, mixins, enums, extensions, top-level variables. Methods are resolved inside the class body — `[[file.dart#Class#method]]` matches any method declaration inside `class Class { ... }`. Mixins are emitted as interface-kind symbols. + Source code is parsed lazily with tree-sitter (via `web-tree-sitter`). Only files referenced by wiki links are parsed — no up-front scanning. [[cli#check#md]] validates that the file exists and the symbol is defined. ### Strict vs Lenient Contexts diff --git a/package.json b/package.json index 9edbd4c..34722fb 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@folder/xdg": "^4.0.1", "@libsql/client": "^0.17.0", "@modelcontextprotocol/sdk": "^1.27.1", - "@repomix/tree-sitter-wasms": "^0.1.16", + "@repomix/tree-sitter-wasms": "^0.1.17", "commander": "^14.0.3", "ignore-walk": "^8.0.0", "mdast-util-to-markdown": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3759de..b8775a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.27.1 version: 1.27.1(zod@4.3.6) '@repomix/tree-sitter-wasms': - specifier: ^0.1.16 - version: 0.1.16 + specifier: ^0.1.17 + version: 0.1.17 commander: specifier: ^14.0.3 version: 14.0.3 @@ -316,8 +316,8 @@ packages: '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - '@repomix/tree-sitter-wasms@0.1.16': - resolution: {integrity: sha512-CIINozBWFwjhH4DQALN/b4n1S08fHhXQOdjX2G7s4w+Urew37aLU0AHVyCjHM5Pbnh63tDYt4YyUkS6vRUV38A==} + '@repomix/tree-sitter-wasms@0.1.17': + resolution: {integrity: sha512-tc3HnFqdMF1pXhIMzG3aTaBDpIiHK2tPfn3fwqA6P3WTbHa+1EuuTubbKshvmN7xCHP5Ojz0/VW4R+XvR88KOw==} '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} @@ -1547,7 +1547,7 @@ snapshots: '@neon-rs/load@0.0.4': {} - '@repomix/tree-sitter-wasms@0.1.16': {} + '@repomix/tree-sitter-wasms@0.1.17': {} '@rollup/rollup-android-arm-eabi@4.59.0': optional: true diff --git a/src/cli/refs.ts b/src/cli/refs.ts index 77b8949..d5f3826 100644 --- a/src/cli/refs.ts +++ b/src/cli/refs.ts @@ -43,6 +43,7 @@ const SOURCE_EXTS = new Set([ '.go', '.c', '.h', + '.dart', ]); /** diff --git a/src/source-parser.ts b/src/source-parser.ts index 46f95d8..f4b8c20 100644 --- a/src/source-parser.ts +++ b/src/source-parser.ts @@ -60,6 +60,7 @@ const grammarMap: Record = { '.go': 'tree-sitter-go.wasm', '.c': 'tree-sitter-c.wasm', '.h': 'tree-sitter-c.wasm', + '.dart': 'tree-sitter-dart.wasm', }; /** All source file extensions that lat can parse (derived from grammarMap). */ @@ -517,6 +518,165 @@ function extractGoSymbols(tree: Tree): SourceSymbol[] { return symbols; } +/** + * Extract the name from a Dart node. The tree-sitter-dart grammar doesn't + * always expose a `name` field — for mixin_declaration the name is a plain + * `identifier` child. For method_signature the name lives inside a nested + * function_signature. This helper tries the field first, then falls back + * to the first `identifier` named child. + */ +function dartName(node: SyntaxNode): string | null { + const field = node.childForFieldName('name'); + if (field) return field.text; + const ident = node.namedChildren.find((c) => c.type === 'identifier'); + return ident ? ident.text : null; +} + +function extractDartSymbols(tree: Tree): SourceSymbol[] { + const symbols: SourceSymbol[] = []; + const root = tree.rootNode; + + for (let i = 0; i < root.childCount; i++) { + const node = root.child(i)!; + const startLine = node.startPosition.row + 1; + const endLine = node.endPosition.row + 1; + + if (node.type === 'function_signature') { + const name = dartName(node); + if (name) { + symbols.push({ + name, + kind: 'function', + startLine, + endLine, + signature: firstLine(node.text), + }); + } + } else if (node.type === 'class_definition') { + const name = dartName(node); + if (name) { + symbols.push({ + name, + kind: 'class', + startLine, + endLine, + signature: firstLine(node.text), + }); + const body = node.childForFieldName('body'); + if (body) { + extractDartClassMembers(body, name, symbols); + } + } + } else if (node.type === 'mixin_declaration') { + const name = dartName(node); + if (name) { + symbols.push({ + name, + kind: 'interface', + startLine, + endLine, + signature: firstLine(node.text), + }); + const body = node.namedChildren.find((c) => c.type === 'class_body'); + if (body) { + extractDartClassMembers(body, name, symbols); + } + } + } else if (node.type === 'extension_declaration') { + const name = dartName(node); + if (name) { + symbols.push({ + name, + kind: 'class', + startLine, + endLine, + signature: firstLine(node.text), + }); + const body = node.namedChildren.find((c) => c.type === 'class_body'); + if (body) { + extractDartClassMembers(body, name, symbols); + } + } + } else if (node.type === 'enum_declaration') { + const name = dartName(node); + if (name) { + symbols.push({ + name, + kind: 'class', + startLine, + endLine, + signature: firstLine(node.text), + }); + const body = node.namedChildren.find( + (c) => c.type === 'enum_body' || c.type === 'class_body', + ); + if (body) { + extractDartClassMembers(body, name, symbols); + } + } + } else if (node.type === 'static_final_declaration_list') { + // Top-level `final x = ...` or `const x = ...` — the list contains + // static_final_declaration children, each with an identifier. + for (let j = 0; j < node.namedChildCount; j++) { + const decl = node.namedChild(j)!; + if (decl.type === 'static_final_declaration') { + const name = dartName(decl); + if (name) { + symbols.push({ + name, + kind: 'variable', + startLine: decl.startPosition.row + 1, + endLine: decl.endPosition.row + 1, + signature: firstLine(decl.text), + }); + } + } + } + } + } + + return symbols; +} + +function extractDartClassMembers( + body: SyntaxNode, + className: string, + symbols: SourceSymbol[], +): void { + for (let i = 0; i < body.namedChildCount; i++) { + const member = body.namedChild(i)!; + if (member.type === 'method_signature') { + // method_signature wraps a function_signature child that holds the name + const funcSig = member.namedChildren.find( + (c) => c.type === 'function_signature', + ); + const name = funcSig ? dartName(funcSig) : null; + if (name) { + symbols.push({ + name, + kind: 'method', + parent: className, + startLine: member.startPosition.row + 1, + endLine: member.endPosition.row + 1, + signature: firstLine(member.text), + }); + } + } else if (member.type === 'function_signature') { + const name = dartName(member); + if (name) { + symbols.push({ + name, + kind: 'method', + parent: className, + startLine: member.startPosition.row + 1, + endLine: member.endPosition.row + 1, + signature: firstLine(member.text), + }); + } + } + } +} + /** * Extract the declarator name from a C function_declarator node. * Handles plain identifiers and pointer declarators (*name). @@ -862,6 +1022,9 @@ export async function parseSourceSymbols( if (ext === '.c' || ext === '.h') { return extractCSymbols(tree); } + if (ext === '.dart') { + return extractDartSymbols(tree); + } return extractTsSymbols(tree); } finally { tree.delete(); diff --git a/templates/AGENTS.md b/templates/AGENTS.md index c544150..b98afc5 100644 --- a/templates/AGENTS.md +++ b/templates/AGENTS.md @@ -35,8 +35,8 @@ If `lat search` fails because no API key is configured, explain to the user that - **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`). - **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`. -- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist. -- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts +- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C/Dart files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct), `[[src/app.dart#Greeter#greet]]` (Dart method). `lat check` validates these exist. +- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C/Dart) or `# @lat: [[section-id]]` (Python) — ties source code to concepts # Test specs diff --git a/templates/cursor-rules.md b/templates/cursor-rules.md index 7bab9a1..ed0e298 100644 --- a/templates/cursor-rules.md +++ b/templates/cursor-rules.md @@ -33,8 +33,8 @@ If `lat_search` fails because `LAT_LLM_KEY` is not set, explain to the user that - **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`). - **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`. -- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist. -- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts +- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C/Dart files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct), `[[src/app.dart#Greeter#greet]]` (Dart method). `lat check` validates these exist. +- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C/Dart) or `# @lat: [[section-id]]` (Python) — ties source code to concepts # Test specs diff --git a/tests/cases.test.ts b/tests/cases.test.ts index b9e0bb8..220b036 100644 --- a/tests/cases.test.ts +++ b/tests/cases.test.ts @@ -390,6 +390,34 @@ describe('python-code-ref', () => { }); }); +// --- dart-code-ref --- + +describe('dart-code-ref', () => { + it('scans @lat refs from Dart // comments including between annotations', async () => { + const { refs } = await scanCodeRefs(caseDir('dart-code-ref')); + expect(refs).toHaveLength(3); + + expect(refs[0].target).toBe('Specs#Feature A'); + expect(refs[0].file).toContain('app.dart'); + expect(refs[0].line).toBe(1); + + expect(refs[1].target).toBe('Specs#Feature B'); + expect(refs[1].file).toContain('app.dart'); + expect(refs[1].line).toBe(5); + + expect(refs[2].target).toBe('Specs#Nonexistent'); + expect(refs[2].line).toBe(8); + }); + + it('detects dangling @lat ref in Dart file', async () => { + const { errors, files } = await checkCodeRefs(latDir('dart-code-ref')); + expect(errors).toHaveLength(1); + expect(errors[0].target).toBe('Specs#Nonexistent'); + expect(errors[0].message).toContain('no matching section found'); + expect(files).toEqual({ '.dart': 1 }); + }); +}); + // --- gitignore-filtering --- describe('gitignore-filtering', () => { @@ -900,6 +928,43 @@ describe('error-source-ref-go-missing', () => { }); }); +describe('source-ref-dart-valid', () => { + it('resolves Dart function, class, method, mixin, enum, and variable refs without errors', async () => { + // docs.md links: greet (func), Greeter (class), Greeter#greet (method), + // createGreeter (func), Greeting (mixin), defaultName (var), Color (enum), + // DotShorthand (class using Dart 3.7 dot shorthand), DotShorthand#pick (method) + const { errors } = await checkMd(latDir('source-ref-dart-valid')); + expect(errors).toHaveLength(0); + }); +}); + +describe('error-source-ref-dart-missing', () => { + it('check md reports all missing Dart symbols', async () => { + const { errors } = await checkMd(latDir('error-source-ref-dart-missing')); + expect(errors).toHaveLength(4); + + const byTarget = new Map(errors.map((e) => [e.target, e])); + + const fn = byTarget.get('src/app.dart#nonexistent')!; + expect(fn).toBeDefined(); + expect(fn.message).toContain('symbol "nonexistent" not found'); + + const cls = byTarget.get('src/app.dart#MissingClass')!; + expect(cls).toBeDefined(); + expect(cls.message).toContain('symbol "MissingClass" not found'); + + const cnst = byTarget.get('src/app.dart#MISSING_CONST')!; + expect(cnst).toBeDefined(); + expect(cnst.message).toContain('symbol "MISSING_CONST" not found'); + + const method = byTarget.get('src/app.dart#Greeter#missingMethod')!; + expect(method).toBeDefined(); + expect(method.message).toContain( + 'symbol "Greeter#missingMethod" not found', + ); + }); +}); + describe('source-ref-c-valid', () => { // @lat: [[tests/check-md#Passes with valid links#Passes with C enum value links]] it('resolves C function, struct, struct field, enum, typedef, define, variable, pointer-returning, and array refs without errors', async () => { diff --git a/tests/cases/dart-code-ref/app.dart b/tests/cases/dart-code-ref/app.dart new file mode 100644 index 0000000..59435b0 --- /dev/null +++ b/tests/cases/dart-code-ref/app.dart @@ -0,0 +1,9 @@ +// @lat: [[Specs#Feature A]] +void doFeatureA() {} + +@deprecated +// @lat: [[Specs#Feature B]] +void doFeatureB() {} + +// @lat: [[Specs#Nonexistent]] +void doMissing() {} diff --git a/tests/cases/dart-code-ref/lat.md/specs.md b/tests/cases/dart-code-ref/lat.md/specs.md new file mode 100644 index 0000000..cab3d79 --- /dev/null +++ b/tests/cases/dart-code-ref/lat.md/specs.md @@ -0,0 +1,9 @@ +# Specs + +## Feature A + +Some text. + +## Feature B + +More text. diff --git a/tests/cases/error-source-ref-dart-missing/lat.md/docs.md b/tests/cases/error-source-ref-dart-missing/lat.md/docs.md new file mode 100644 index 0000000..c917692 --- /dev/null +++ b/tests/cases/error-source-ref-dart-missing/lat.md/docs.md @@ -0,0 +1,9 @@ +# Docs + +Missing function: [[src/app.dart#nonexistent]]. + +Missing class: [[src/app.dart#MissingClass]]. + +Missing const: [[src/app.dart#MISSING_CONST]]. + +Missing method: [[src/app.dart#Greeter#missingMethod]]. diff --git a/tests/cases/error-source-ref-dart-missing/src/app.dart b/tests/cases/error-source-ref-dart-missing/src/app.dart new file mode 100644 index 0000000..acb90a3 --- /dev/null +++ b/tests/cases/error-source-ref-dart-missing/src/app.dart @@ -0,0 +1,15 @@ +String greet(String name) { + return 'Hello, $name!'; +} + +class Greeter { + final String prefix; + + Greeter(this.prefix); + + String greet(String name) { + return '$prefix $name!'; + } +} + +final defaultName = 'World'; diff --git a/tests/cases/source-ref-dart-valid/lat.md/docs.md b/tests/cases/source-ref-dart-valid/lat.md/docs.md new file mode 100644 index 0000000..1a62e7b --- /dev/null +++ b/tests/cases/source-ref-dart-valid/lat.md/docs.md @@ -0,0 +1,15 @@ +# Docs + +See [[src/app.dart#greet]] for the greeting function. + +The [[src/app.dart#Greeter]] class has a method [[src/app.dart#Greeter#greet]]. + +Create one with [[src/app.dart#createGreeter]]. + +The [[src/app.dart#Greeting]] mixin defines shared behavior. + +Default name is [[src/app.dart#defaultName]]. + +The [[src/app.dart#Color]] enum lists available colors. + +The [[src/app.dart#DotShorthand]] class exercises Dart 3.7 dot shorthand via [[src/app.dart#DotShorthand#pick]]. diff --git a/tests/cases/source-ref-dart-valid/src/app.dart b/tests/cases/source-ref-dart-valid/src/app.dart new file mode 100644 index 0000000..502c5d3 --- /dev/null +++ b/tests/cases/source-ref-dart-valid/src/app.dart @@ -0,0 +1,29 @@ +String greet(String name) { + return 'Hello, $name!'; +} + +class Greeter { + final String prefix; + + Greeter(this.prefix); + + String greet(String name) { + return '$prefix $name!'; + } +} + +Greeter createGreeter(String prefix) { + return Greeter(prefix); +} + +mixin Greeting { + String hello() => 'Hi there!'; +} + +final defaultName = 'World'; + +enum Color { red, green, blue } + +class DotShorthand { + Color pick() => .red; +}