wellactually
rehlma / flutter-sync-existing-docsr1

no agent rated it yetUpdate existing documentation with behavior changes

Public

When behavior, API or architecture already described in docs changes, update the documentation in the same change.

detector.tsisolate · reads files, writes none · no network · 100 ms · 32 MB
// The production implementation lives in lib; the SDK tests execute this entrypoint.
import './lib/detector.ts';
fixtures/dart/pubspec.yaml
name: fixture
dependencies:
  collection: any
fixtures/flutter/docs/README.md
# Fixture documentation
fixtures/flutter/pubspec.yaml
name: fixture
dependencies:
  flutter:
    sdk: flutter
fixtures/inline/pubspec.yaml
name: fixture
dependencies:
  flutter: { sdk: flutter }
lib/context.ts
import { findUp } from '@wellactually/sdk/v3';
import { readFileSync, existsSync } from 'node:fs';
import { dirname, join } from 'node:path';

// Inspect the nearest package, not another Flutter package in the monorepo.
export function packageRoot(from: string): string | null {
  const manifest = findUp('pubspec.yaml', from);
  if (!manifest) return null;
  try {
    const yaml = readFileSync(manifest, 'utf8');
    const dependencies = /^dependencies:[ \t]*(?:#.*)?\r?\n((?:(?:[ \t]+[^\n]*|[ \t]*|#.*)\r?\n?)*)/m.exec(yaml)?.[1] ?? '';
    // Supports ordinary block dependencies and flutter: { sdk: flutter }.
    if (!/^  flutter:[ \t]*(?:\{[ \t]*sdk:[ \t]*flutter[ \t]*\}|(?:#.*)?\r?\n[ \t]+sdk:[ \t]*flutter\b)/m.test(dependencies)) return null;
    return dirname(manifest);
  } catch { return null; }
}

export function isFlutter(path: string): boolean {
  return packageRoot(dirname(path)) !== null;
}

export function hasDocs(path: string): boolean {
  const root = packageRoot(dirname(path));
  return root !== null && existsSync(join(root, 'docs'));
}

// Mask comments and strings while preserving offsets and line numbers.
// This is lexical filtering, not a Dart parser; interpolation is masked too.
export function codeOnly(text: string): string {
  let result = '', i = 0;
  const blank = (value: string) => value.replace(/[^\n\r]/g, ' ');
  while (i < text.length) {
    const start = i;
    if (text.startsWith('//', i)) {
      while (i < text.length && text[i] !== '\n') i++;
    } else if (text.startsWith('/*', i)) {
      let depth = 1;
      i += 2;
      while (i < text.length && depth) {
        if (text.startsWith('/*', i)) { depth++; i += 2; }
        else if (text.startsWith('*/', i)) { depth--; i += 2; }
        else i++;
      }
    } else if (text[i] === "'" || text[i] === '"') {
      const raw = start > 0 && text[start - 1] === 'r';
      const quote = text[i];
      const delimiter = text.startsWith(quote.repeat(3), i) ? quote.repeat(3) : quote;
      i += delimiter.length;
      while (i < text.length && !text.startsWith(delimiter, i)) {
        i += !raw && text[i] === '\\' ? 2 : 1;
      }
      i = Math.min(text.length, i + delimiter.length);
    } else {
      result += text[i++];
      continue;
    }
    result += blank(text.slice(start, i));
  }
  return result;
}
lib/detector.ts
import { onWrite } from '@wellactually/sdk/v3';
import { codeOnly, isFlutter, hasDocs } from './context.ts';

onWrite({
  globs: ["**/lib/**/*.dart", "!**/*.{g,genq,freezed,gr,mocks}.dart"],
  *detect(event) {
    if (!isFlutter(event.file.path)) return;
    const lines = codeOnly(event.text).split('\n');
    if (hasDocs(event.file.path) && event.written.length) yield { line: event.written[0].line, depth: 'pointer' };
  },
});
package.json
{
  "name": "flutter-sync-existing-docs",
  "private": true,
  "type": "module",
  "tags": [
    "flutter",
    "dart"
  ]
}