import 'package:build/build.dart'; import 'package:code_snippets/builder.dart'; import 'package:code_snippets/highlight.dart'; import 'package:source_span/source_span.dart'; import 'package:sqlparser/sqlparser.dart'; class SnippetsBuilder extends CodeExcerptBuilder { // ignore: avoid_unused_constructor_parameters SnippetsBuilder([BuilderOptions? options]) : super( dropIndendation: true, overriddenDartDocUrls: { // For CI builds, the dartdoc output for the drift package is added // under the `api/` url. if (options?.config['release'] == true) 'drift': Uri.parse('/api/'), }, ); @override bool shouldEmitFor(AssetId input, Excerpter excerpts) { return true; } @override Future highlighterFor( AssetId assetId, String content, BuildStep buildStep) async { switch (assetId.extension) { case '.drift': return _DriftHighlighter( SourceFile.fromString(content, url: assetId.uri)); default: return super.highlighterFor(assetId, content, buildStep); } } } class _DriftHighlighter extends Highlighter { _DriftHighlighter(SourceFile file) : super(file); @override void highlight() { final engine = SqlEngine( EngineOptions( driftOptions: const DriftSqlOptions(), version: SqliteVersion.current, ), ); final result = engine.parseDriftFile(file.span(0).text); _HighlightingVisitor().visit(result.rootNode, this); for (final token in result.tokens) { const ignoredKeyword = [ TokenType.$null, TokenType.$true, TokenType.$false ]; if (token is KeywordToken && !ignoredKeyword.contains(token.type) && !token.isIdentifier) { reportSql(token, RegionType.keyword); } else if (token is CommentToken) { reportSql(token, RegionType.comment); } else if (token is StringLiteralToken) { reportSql(token, RegionType.string); } } } void reportSql(SyntacticEntity? entity, RegionType type) { if (entity != null) { report(HighlightRegion( type, file.span(entity.firstPosition, entity.lastPosition))); } } } class _HighlightingVisitor extends RecursiveVisitor<_DriftHighlighter, void> { @override void visitCreateTriggerStatement( CreateTriggerStatement e, _DriftHighlighter arg) { arg.reportSql(e.triggerNameToken, RegionType.classTitle); visitChildren(e, arg); } @override void visitCreateViewStatement(CreateViewStatement e, _DriftHighlighter arg) { arg.reportSql(e.viewNameToken, RegionType.classTitle); visitChildren(e, arg); } @override void visitColumnDefinition(ColumnDefinition e, _DriftHighlighter arg) { arg ..reportSql(e.nameToken, RegionType.variable) ..reportSql(e.typeNames?.toSingleEntity, RegionType.type); visitChildren(e, arg); } @override void visitColumnConstraint(ColumnConstraint e, _DriftHighlighter arg) { if (e is NotNull) { arg.reportSql(e.$null, RegionType.keyword); } else if (e is NullColumnConstraint) { arg.reportSql(e.$null, RegionType.keyword); } super.visitColumnConstraint(e, arg); } @override void visitNullLiteral(NullLiteral e, _DriftHighlighter arg) { arg.reportSql(e, RegionType.builtIn); } @override void visitNumericLiteral(NumericLiteral e, _DriftHighlighter arg) { arg.reportSql(e, RegionType.number); } @override void visitDriftSpecificNode(DriftSpecificNode e, _DriftHighlighter arg) { if (e is DeclaredStatement) { final name = e.identifier; if (name is SimpleName) { arg.reportSql(name.identifier, RegionType.functionTitle); } } super.visitDriftSpecificNode(e, arg); } @override void visitBooleanLiteral(BooleanLiteral e, _DriftHighlighter arg) { arg.reportSql(e, RegionType.literal); } @override void visitReference(Reference e, _DriftHighlighter arg) { arg.reportSql(e, RegionType.variable); } @override void visitTableReference(TableReference e, _DriftHighlighter arg) { arg.reportSql(e.tableNameToken, RegionType.type); } @override void visitTableInducingStatement( TableInducingStatement e, _DriftHighlighter arg) { arg.reportSql(e.tableNameToken, RegionType.classTitle); if (e is CreateVirtualTableStatement) { arg.reportSql(e.moduleNameToken, RegionType.invokedFunctionTitle); } visitChildren(e, arg); } @override void visitVariable(Variable e, _DriftHighlighter arg) { arg.reportSql(e, RegionType.variable); } }