diff --git a/build.gradle.kts b/build.gradle.kts index d0f2ceb3..eda38c86 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -185,6 +185,7 @@ dependencies { jacocoAggregation(project(":chapi-ast-cpp")) jacocoAggregation(project(":chapi-ast-protobuf")) jacocoAggregation(project(":chapi-ast-swift")) + jacocoAggregation(project(":chapi-ast-dart")) jacocoAggregation(project(":chapi-parser-toml")) jacocoAggregation(project(":chapi-parser-cmake")) diff --git a/chapi-ast-dart/build.gradle.kts b/chapi-ast-dart/build.gradle.kts new file mode 100644 index 00000000..3c46f8cf --- /dev/null +++ b/chapi-ast-dart/build.gradle.kts @@ -0,0 +1,61 @@ +plugins { + id("antlr") + java + kotlin("jvm") + kotlin("plugin.serialization") version "1.9.24" + + `jacoco-conventions` +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + antlr("org.antlr:antlr4:4.13.2") + + // project deps + implementation(project(":chapi-domain")) + + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + + implementation(kotlin("stdlib-jdk8")) + implementation(kotlin("reflect")) + // Kotlin reflection. + testImplementation(kotlin("test")) + + // JUnit 5 + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2") + testRuntimeOnly("org.junit.platform:junit-platform-console:1.10.2") + + implementation("org.antlr:antlr4:4.13.2") + implementation("org.antlr:antlr4-runtime:4.13.2") +} + +sourceSets.main { + // Append generated-src to existing source directories (don't replace src/main/java) + java.srcDir("${project.layout.buildDirectory.get().asFile}/generated-src") +} + +tasks.generateGrammarSource { + maxHeapSize = "128m" + arguments = arguments + listOf("-package", "chapi.ast.antlr") + listOf("-visitor", "-long-messages") + outputDirectory = file("${project.layout.buildDirectory.get().asFile}/generated-src/chapi/ast/antlr") +} + +tasks.withType { + +} + +tasks.named("compileKotlin") { + dependsOn(tasks.withType()) +} + +tasks.withType { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } +} diff --git a/chapi-ast-dart/src/main/antlr/Dart2Lexer.g4 b/chapi-ast-dart/src/main/antlr/Dart2Lexer.g4 new file mode 100644 index 00000000..c53dcd9a --- /dev/null +++ b/chapi-ast-dart/src/main/antlr/Dart2Lexer.g4 @@ -0,0 +1,245 @@ +/* Generated Mon, Jun 13, 2022 8:11:58 AM EST + * + * Copyright (c) 2022, 2023 Ken Domino + * Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + * + * This grammar is generated from the CFG contained in: + * https://github.com/dart-lang/language/blob/70eb85cf9a6606a9da0de824a5d55fd06de1287f/specification/dartLangSpec.tex + * + * The bash script used to scrape and the refactor the grammar is here: + * https://github.com/kaby76/ScrapeDartSpec/blob/master/refactor.sh + * + * Note: the CFG in the Specification is in development, and is for approximately + * Dart version 2.15. The Specification is not up-to-date vis-a-vis the actual + * compiler code, located here: + * https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared/lib/src/parser + * Some of the refactorings that are applied are to bring the code into a working + * Antlr4 parser. Other refactorings replace some of the rules in the Spec because + * the Spec is incorrect, or incomplete. + * + * This grammar has been checked against a large subset (~370 Dart files) of the Dart SDK: + * https://github.com/dart-lang/sdk/tree/main/sdk/lib + * A copy of the SDK is provided in the examples for regression testing. + */ + +// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine +// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true + +lexer grammar Dart2Lexer; + +options { + superClass = Dart2LexerBase; +} + +// Insert here @header for C++ lexer. + +A : '&'; +AA : '&&'; +AE : '&='; +AT : '@'; +C : ','; +CB : ']'; +CBC : '}'; +CIR : '^'; +CIRE : '^='; +CO : ':'; +CP : ')'; +D : '.'; +DD : '..'; +DDD : '...'; +DDDQ : '...?'; +EE : '=='; +EG : '=>'; +EQ : '='; +GT : '>'; +LT : '<'; +LTE : '<='; +LTLT : '<<'; +LTLTE : '<<='; +ME : '-='; +MINUS : '-'; +MM : '--'; +NE : '!='; +NOT : '!'; +OB : '['; +OBC : '{'; +OP : '('; +P : '|'; +PC : '%'; +PE : '%='; +PL : '+'; +PLE : '+='; +PLPL : '++'; +PO : '#'; +POE : '|='; +PP : '||'; +QU : '?'; +QUD : '?.'; +QUDD : '?..'; +QUQU : '??'; +QUQUEQ : '??='; +SC : ';'; +SE : '/='; +SL : '/'; +SQS : '~/'; +SQSE : '~/='; +SQUIG : '~'; +ST : '*'; +STE : '*='; +ABSTRACT_ : 'abstract'; +AS_ : 'as'; +ASSERT_ : 'assert'; +ASYNC_ : 'async'; +AWAIT_ : 'await'; +BREAK_ : 'break'; +CASE_ : 'case'; +CATCH_ : 'catch'; +CLASS_ : 'class'; +CONST_ : 'const'; +CONTINUE_ : 'continue'; +COVARIANT_ : 'covariant'; +DEFAULT_ : 'default'; +DEFERRED_ : 'deferred'; +DO_ : 'do'; +DYNAMIC_ : 'dynamic'; +ELSE_ : 'else'; +ENUM_ : 'enum'; +EXPORT_ : 'export'; +EXTENDS_ : 'extends'; +EXTENSION_ : 'extension'; +EXTERNAL_ : 'external'; +FACTORY_ : 'factory'; +FALSE_ : 'false'; +FINAL_ : 'final'; +FINALLY_ : 'finally'; +FOR_ : 'for'; +FUNCTION_ : 'Function'; +GET_ : 'get'; +GTILDE_ : 'gtilde'; +HIDE_ : 'hide'; +IF_ : 'if'; +IMPLEMENTS_ : 'implements'; +IMPORT_ : 'import'; +IN_ : 'in'; +INTERFACE_ : 'interface'; +IS_ : 'is'; +LATE_ : 'late'; +LET_ : 'let'; +LIBRARY_ : 'library'; +MIXIN_ : 'mixin'; +NATIVE_ : 'native'; +NEW_ : 'new'; +NULL_ : 'null'; +OF_ : 'of'; +ON_ : 'on'; +OPERATOR_ : 'operator'; +PART_ : 'part'; +REQUIRED_ : 'required'; +RETHROW_ : 'rethrow'; +RETURN_ : 'return'; +// Dart 3 class modifiers +SEALED_ : 'sealed'; +BASE_ : 'base'; +SET_ : 'set'; +SHOW_ : 'show'; +STATIC_ : 'static'; +SUPER_ : 'super'; +SWITCH_ : 'switch'; +SYNC_ : 'sync'; +THIS_ : 'this'; +THROW_ : 'throw'; +TRUE_ : 'true'; +TRY_ : 'try'; +TYPEDEF_ : 'typedef'; +VAR_ : 'var'; +VOID_ : 'void'; +WHILE_ : 'while'; +WITH_ : 'with'; +YIELD_ : 'yield'; +NUMBER : DIGIT+ ( '.' DIGIT+)? EXPONENT? | '.' DIGIT+ EXPONENT?; +HEX_NUMBER : '0x' HEX_DIGIT+ | '0X' HEX_DIGIT+; +SingleLineString: + StringDQ + | StringSQ + | 'r\'' ~('\'' | '\n' | '\r')* '\'' + | 'r"' ~('"' | '\n' | '\r')* '"' +; +MultiLineString: + '"""' StringContentTDQ*? '"""' + | '\'\'\'' StringContentTSQ*? '\'\'\'' + | 'r"""' (~'"' | '"' ~'"' | '""' ~'"')* '"""' + | 'r\'\'\'' (~'\'' | '\'' ~'\'' | '\'\'' ~'\'')* '\'\'\'' +; +IDENTIFIER : IDENTIFIER_START IDENTIFIER_PART*; +WHITESPACE : ( '\t' | ' ' | NEWLINE)+ -> skip; +SINGLE_LINE_COMMENT : '//' ~[\r\n]* -> skip; +MULTI_LINE_COMMENT : '/*' ( MULTI_LINE_COMMENT | .)*? '*/' -> skip; +fragment EXPONENT : ( 'e' | 'E') ( '+' | '-')? DIGIT+; +fragment HEX_DIGIT : 'a' .. 'f' | 'A' .. 'F' | DIGIT; +fragment StringDQ : '"' StringContentDQ*? '"'; +fragment StringContentDQ: + ~('\\' | '"' | '\n' | '\r' | '$') + | '\\' ~('\n' | '\r') + | StringDQ + | '${' StringContentDQ*? '}' + | '$' { this.CheckNotOpenBrace() }? +; +fragment StringSQ: '\'' StringContentSQ*? '\''; +fragment StringContentSQ: + ~('\\' | '\'' | '\n' | '\r' | '$') + | '\\' ~('\n' | '\r') + | StringSQ + | '${' StringContentSQ*? '}' + | '$' { this.CheckNotOpenBrace() }? +; +fragment StringContentTDQ : ~('\\' | '"') | '"' ~'"' | '""' ~'"'; +fragment StringContentTSQ : '\'' ~'\'' | '\'\'' ~'\'' | .; +fragment ESCAPE_SEQUENCE: + '\n' + | '\r' + | '\\f' + | '\\b' + | '\t' + | '\\v' + | '\\x' HEX_DIGIT HEX_DIGIT + | '\\u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + | '\\u{' HEX_DIGIT_SEQUENCE '}' +; +fragment HEX_DIGIT_SEQUENCE : HEX_DIGIT HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? HEX_DIGIT?; +fragment NEWLINE : '\n' | '\r' | '\r\n'; +fragment BUILT_IN_IDENTIFIER: + 'abstract' + | 'as' + | 'covariant' + | 'deferred' + | 'dynamic' + | 'export' + | 'external' + | 'extension' + | 'factory' + | 'Function' + | 'get' + | 'implements' + | 'import' + | 'interface' + | 'late' + | 'library' + | 'mixin' + | 'operator' + | 'part' + | 'required' + | 'set' + | 'static' + | 'typedef' +; +fragment OTHER_IDENTIFIER : 'async' | 'hide' | 'of' | 'on' | 'show' | 'sync' | 'await' | 'yield'; +fragment IDENTIFIER_NO_DOLLAR : IDENTIFIER_START_NO_DOLLAR IDENTIFIER_PART_NO_DOLLAR*; +fragment IDENTIFIER_START_NO_DOLLAR : LETTER | '_'; +fragment IDENTIFIER_PART_NO_DOLLAR : IDENTIFIER_START_NO_DOLLAR | DIGIT; +fragment IDENTIFIER_START : IDENTIFIER_START_NO_DOLLAR | '$'; +fragment IDENTIFIER_PART : IDENTIFIER_START | DIGIT; +fragment LETTER : 'a' .. 'z' | 'A' .. 'Z'; +fragment DIGIT : '0' .. '9'; \ No newline at end of file diff --git a/chapi-ast-dart/src/main/antlr/Dart2Parser.g4 b/chapi-ast-dart/src/main/antlr/Dart2Parser.g4 new file mode 100644 index 00000000..09987da4 --- /dev/null +++ b/chapi-ast-dart/src/main/antlr/Dart2Parser.g4 @@ -0,0 +1,1138 @@ +/* Generated Mon, Jun 13, 2022 8:11:58 AM EST + * + * Copyright (c) 2022, 2023 Ken Domino + * Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + * + * This grammar is generated from the CFG contained in: + * https://github.com/dart-lang/language/blob/70eb85cf9a6606a9da0de824a5d55fd06de1287f/specification/dartLangSpec.tex + * + * The bash script used to scrape and the refactor the grammar is here: + * https://github.com/kaby76/ScrapeDartSpec/blob/master/refactor.sh + * + * Note: the CFG in the Specification is in development, and is for approximately + * Dart version 2.15. The Specification is not up-to-date vis-a-vis the actual + * compiler code, located here: + * https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared/lib/src/parser + * Some of the refactorings that are applied are to bring the code into a working + * Antlr4 parser. Other refactorings replace some of the rules in the Spec because + * the Spec is incorrect, or incomplete. + * + * This grammar has been checked against a large subset (~370 Dart files) of the Dart SDK: + * https://github.com/dart-lang/sdk/tree/main/sdk/lib + * A copy of the SDK is provided in the examples for regression testing. + */ + +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +parser grammar Dart2Parser; + +options { + tokenVocab = Dart2Lexer; +} + +additiveExpression + : multiplicativeExpression (additiveOperator multiplicativeExpression)* + | SUPER_ ( additiveOperator multiplicativeExpression)+ + ; + +additiveOperator + : PL + | MINUS + ; + +argumentList + : namedArgument (C namedArgument)* + | expressionList ( C namedArgument)* + ; + +argumentPart + : typeArguments? arguments + ; + +arguments + : OP (argumentList C?)? CP + ; + +asOperator + : AS_ + ; + +assertion + : ASSERT_ OP expr (C expr)? C? CP + ; + +assertStatement + : assertion SC + ; + +assignableExpression + : primary assignableSelectorPart + | SUPER_ unconditionalAssignableSelector + | identifier + ; + +assignableSelector + : unconditionalAssignableSelector + | QUD identifier + | QU OB expr CB + ; + +assignableSelectorPart + : selector* assignableSelector + ; + +assignmentOperator + : EQ + | compoundAssignmentOperator + ; + +awaitExpression + : AWAIT_ unaryExpression + ; + +binaryOperator + : multiplicativeOperator + | additiveOperator + | shiftOperator + | relationalOperator + | EE + | bitwiseOperator + ; + +bitwiseAndExpression + : shiftExpression (A shiftExpression)* + | SUPER_ ( A shiftExpression)+ + ; + +bitwiseOperator + : A + | CIR + | P + ; + +bitwiseOrExpression + : bitwiseXorExpression (P bitwiseXorExpression)* + | SUPER_ ( P bitwiseXorExpression)+ + ; + +bitwiseXorExpression + : bitwiseAndExpression (CIR bitwiseAndExpression)* + | SUPER_ ( CIR bitwiseAndExpression)+ + ; + +block + : OBC statements CBC + ; + +booleanLiteral + : TRUE_ + | FALSE_ + ; + +breakStatement + : BREAK_ identifier? SC + ; + +cascade + : cascade DD cascadeSection + | conditionalExpression ( QUDD | DD) cascadeSection + ; + +cascadeAssignment + : assignmentOperator expressionWithoutCascade + ; + +cascadeSection + : cascadeSelector cascadeSectionTail + ; + +cascadeSectionTail + : cascadeAssignment + | selector* ( assignableSelector cascadeAssignment)? + ; + +cascadeSelector + : OB expr CB + | identifier + ; + +catchPart + : CATCH_ OP identifier (C identifier)? CP + ; + +// Dart 3 class modifiers: sealed, base, interface, final, mixin +classModifier + : ABSTRACT_ + | SEALED_ + | BASE_ + | INTERFACE_ + | FINAL_ + | MIXIN_ + ; + +classDeclaration + : classModifier* CLASS_ typeIdentifier typeParameters? superclass? interfaces? OBC ( + metadata classMemberDeclaration + )* CBC + | classModifier* CLASS_ mixinApplicationClass + ; + +classMemberDeclaration + : declaration SC + | methodSignature functionBody + ; + +combinator + : SHOW_ identifierList + | HIDE_ identifierList + ; + +compilationUnit + : (libraryDeclaration | partDeclaration | expr | statement) EOF + ; + +compoundAssignmentOperator + : STE + | SE + | SQSE + | PE + | PLE + | ME + | LTLTE + | GT GT GT EQ + | GT GT EQ + | AE + | CIRE + | POE + | QUQUEQ + ; + +conditionalExpression + : ifNullExpression (QU expressionWithoutCascade CO expressionWithoutCascade)? + ; + +configurableUri + : uri configurationUri* + ; + +configurationUri + : IF_ OP uriTest CP uri + ; + +constantConstructorSignature + : CONST_ constructorName formalParameterList + ; + +constObjectExpression + : CONST_ constructorDesignation arguments + ; + +constructorDesignation + : typeIdentifier + | qualifiedName + | typeName typeArguments ( D identifier)? + ; + +constructorInvocation + : typeName typeArguments D identifier arguments + ; + +constructorName + : typeIdentifier (D identifier)? + ; + +constructorSignature + : constructorName formalParameterList + ; + +continueStatement + : CONTINUE_ identifier? SC + ; + +declaration + : ABSTRACT_? ( + EXTERNAL_ factoryConstructorSignature + | EXTERNAL_ constantConstructorSignature + | EXTERNAL_ constructorSignature + | ( EXTERNAL_ STATIC_?)? getterSignature + | ( EXTERNAL_ STATIC_?)? setterSignature + | ( EXTERNAL_ STATIC_?)? functionSignature + | EXTERNAL_? operatorSignature + | STATIC_ CONST_ type? staticFinalDeclarationList + | STATIC_ FINAL_ type? staticFinalDeclarationList + | STATIC_ LATE_ FINAL_ type? initializedIdentifierList + | STATIC_ LATE_? varOrType initializedIdentifierList + | COVARIANT_ LATE_ FINAL_ type? identifierList + | COVARIANT_ LATE_? varOrType initializedIdentifierList + | LATE_? FINAL_ type? initializedIdentifierList + | LATE_? varOrType initializedIdentifierList + | redirectingFactoryConstructorSignature + | constantConstructorSignature ( redirection | initializers)? + | constructorSignature ( redirection | initializers)? + ) + ; + +declaredIdentifier + : COVARIANT_? finalConstVarOrType identifier + ; + +defaultCase + : label* DEFAULT_ CO statements + ; + +defaultFormalParameter + : normalFormalParameter (EQ expr)? + ; + +defaultNamedParameter + : metadata REQUIRED_? normalFormalParameterNoMetadata (( EQ | CO) expr)? + ; + +doStatement + : DO_ statement WHILE_ OP expr CP SC + ; + +dottedIdentifierList + : identifier (D identifier)* + ; + +element + : expressionElement + | mapElement + | spreadElement + | ifElement + | forElement + ; + +elements + : element (C element)* C? + ; + +enumEntry + : metadata identifier + ; + +enumType + : ENUM_ identifier OBC enumEntry (C enumEntry)* C? CBC + ; + +equalityExpression + : relationalExpression (equalityOperator relationalExpression)? + | SUPER_ equalityOperator relationalExpression + ; + +equalityOperator + : EE + | NE + ; + +expr + : assignableExpression assignmentOperator expr + | conditionalExpression + | cascade + | throwExpression + ; + +expressionElement + : expr + ; + +expressionList + : expr (C expr)* + ; + +expressionStatement + : expr? SC + ; + +expressionWithoutCascade + : assignableExpression assignmentOperator expressionWithoutCascade + | conditionalExpression + | throwExpressionWithoutCascade + ; + +extensionDeclaration + : EXTENSION_ identifier? typeParameters? ON_ type OBC (metadata classMemberDeclaration)* CBC + ; + +factoryConstructorSignature + : CONST_? FACTORY_ constructorName formalParameterList + ; + +fieldFormalParameter + : finalConstVarOrType? THIS_ D identifier (formalParameterPart QU?)? + ; + +fieldInitializer + : (THIS_ D)? identifier EQ initializerExpression + ; + +finalConstVarOrType + : LATE_? FINAL_ type? + | CONST_ type? + | LATE_? varOrType + ; + +finallyPart + : FINALLY_ block + ; + +forElement + : AWAIT_? FOR_ OP forLoopParts CP element + ; + +forInitializerStatement + : localVariableDeclaration + | expr? SC + ; + +forLoopParts + : forInitializerStatement expr? SC expressionList? + | metadata declaredIdentifier IN_ expr + | identifier IN_ expr + ; + +formalParameterList + : OP CP + | OP normalFormalParameters C? CP + | OP normalFormalParameters C optionalOrNamedFormalParameters CP + | OP optionalOrNamedFormalParameters CP + ; + +formalParameterPart + : typeParameters? formalParameterList + ; + +forStatement + : AWAIT_? FOR_ OP forLoopParts CP statement + ; + +functionBody + : NATIVE_ stringLiteral? SC + | ASYNC_? EG expr SC + | ( ASYNC_ ST? | SYNC_ ST)? block + ; + +functionExpression + : formalParameterPart functionExpressionBody + ; + +functionExpressionBody + : ASYNC_? EG expr + | ( ASYNC_ ST? | SYNC_ ST)? block + ; + +functionFormalParameter + : COVARIANT_? type? identifier formalParameterPart QU? + ; + +functionPrefix + : type? identifier + ; + +functionSignature + : type? identifier formalParameterPart + ; + +functionType + : functionTypeTails + | typeNotFunction functionTypeTails + ; + +functionTypeAlias + : functionPrefix formalParameterPart SC + ; + +functionTypeTail + : FUNCTION_ typeParameters? parameterTypeList + ; + +functionTypeTails + : functionTypeTail QU? functionTypeTails + | functionTypeTail + ; + +getterSignature + : type? GET_ identifier + ; + +identifier + : IDENTIFIER + | ABSTRACT_ + | AS_ + | COVARIANT_ + | DEFERRED_ + | DYNAMIC_ + | EXPORT_ + | EXTERNAL_ + | EXTENSION_ + | FACTORY_ + | FUNCTION_ + | GET_ + | IMPLEMENTS_ + | IMPORT_ + | INTERFACE_ + | LATE_ + | LIBRARY_ + | MIXIN_ + | OPERATOR_ + | PART_ + | REQUIRED_ + | SET_ + | STATIC_ + | TYPEDEF_ + | ASYNC_ + | HIDE_ + | OF_ + | ON_ + | SHOW_ + | SYNC_ + | AWAIT_ + | YIELD_ + | NATIVE_ + | BASE_ + | SEALED_ + ; + +identifierList + : identifier (C identifier)* + ; + +ifElement + : IF_ OP expr CP element (ELSE_ element)? + ; + +ifNullExpression + : logicalOrExpression (QUQU logicalOrExpression)* + ; + +ifStatement + : IF_ OP expr CP statement (ELSE_ statement)? + ; + +importOrExport + : libraryImport + | libraryExport + ; + +importSpecification + : IMPORT_ configurableUri (DEFERRED_? AS_ identifier)? combinator* SC + ; + +incrementOperator + : PLPL + | MM + ; + +initializedIdentifier + : identifier (EQ expr)? + ; + +initializedIdentifierList + : initializedIdentifier (C initializedIdentifier)* + ; + +initializedVariableDeclaration + : declaredIdentifier (EQ expr)? (C initializedIdentifier)* + ; + +initializerExpression + : conditionalExpression + | cascade + ; + +initializerListEntry + : SUPER_ arguments + | SUPER_ D identifier arguments + | fieldInitializer + | assertion + ; + +initializers + : CO initializerListEntry (C initializerListEntry)* + ; + +interfaces + : IMPLEMENTS_ typeNotVoidList + ; + +isOperator + : IS_ NOT? + ; + +label + : identifier CO + ; + +letExpression + : LET_ staticFinalDeclarationList IN_ expr + ; + +libraryDeclaration + : libraryName? importOrExport* partDirective* (metadata topLevelDeclaration)* + ; + +libraryExport + : metadata EXPORT_ configurableUri combinator* SC + ; + +libraryImport + : metadata importSpecification + ; + +libraryName + : metadata LIBRARY_ dottedIdentifierList SC + ; + +listLiteral + : CONST_? typeArguments? OB elements? CB + ; + +literal + : nullLiteral + | booleanLiteral + | numericLiteral + | stringLiteral + | symbolLiteral + | listLiteral + | setOrMapLiteral + ; + +localFunctionDeclaration + : metadata functionSignature functionBody + ; + +localVariableDeclaration + : metadata initializedVariableDeclaration SC + ; + +logicalAndExpression + : equalityExpression (AA equalityExpression)* + ; + +logicalOrExpression + : logicalAndExpression (PP logicalAndExpression)* + ; + +mapElement + : expr CO expr + ; + +metadata + : (AT metadatum)* + ; + +metadatum + : identifier + | qualifiedName + | constructorDesignation arguments + ; + +methodSignature + : constructorSignature initializers? + | factoryConstructorSignature + | STATIC_? functionSignature + | STATIC_? getterSignature + | STATIC_? setterSignature + | operatorSignature + ; + +minusOperator + : MINUS + ; + +mixinApplication + : typeNotVoid mixins interfaces? + ; + +mixinApplicationClass + : identifier typeParameters? EQ mixinApplication SC + ; + +// Dart 3: base mixin, sealed mixin +mixinDeclaration + : (BASE_ | SEALED_)? MIXIN_ typeIdentifier typeParameters? (ON_ typeNotVoidList)? interfaces? OBC ( + metadata classMemberDeclaration + )* CBC + ; + +mixins + : WITH_ typeNotVoidList + ; + +multilineString + : MultiLineString + ; + +multiplicativeExpression + : unaryExpression (multiplicativeOperator unaryExpression)* + | SUPER_ ( multiplicativeOperator unaryExpression)+ + ; + +multiplicativeOperator + : ST + | SL + | PC + | SQS + ; + +namedArgument + : label expr + ; + +namedFormalParameters + : OBC defaultNamedParameter (C defaultNamedParameter)* C? CBC + ; + +namedParameterType + : metadata REQUIRED_? typedIdentifier + ; + +namedParameterTypes + : OBC namedParameterType (C namedParameterType)* C? CBC + ; + +negationOperator + : NOT + ; + +newExpression + : NEW_ constructorDesignation arguments + ; + +nonLabelledStatement + : block + | localVariableDeclaration + | forStatement + | whileStatement + | doStatement + | switchStatement + | ifStatement + | rethrowStatement + | tryStatement + | breakStatement + | continueStatement + | returnStatement + | yieldStatement + | yieldEachStatement + | expressionStatement + | assertStatement + | localFunctionDeclaration + ; + +normalFormalParameter + : metadata normalFormalParameterNoMetadata + ; + +normalFormalParameterNoMetadata + : functionFormalParameter + | fieldFormalParameter + | simpleFormalParameter + ; + +normalFormalParameters + : normalFormalParameter (C normalFormalParameter)* + ; + +normalParameterType + : metadata typedIdentifier + | metadata type + ; + +normalParameterTypes + : normalParameterType (C normalParameterType)* + ; + +nullLiteral + : NULL_ + ; + +numericLiteral + : NUMBER + | HEX_NUMBER + ; + +onPart + : catchPart block + | ON_ typeNotVoid catchPart? block + ; + +operator + : SQUIG + | binaryOperator + | OB CB + | OB CB EQ + ; + +operatorSignature + : type? OPERATOR_ operator formalParameterList + ; + +optionalOrNamedFormalParameters + : optionalPositionalFormalParameters + | namedFormalParameters + ; + +optionalParameterTypes + : optionalPositionalParameterTypes + | namedParameterTypes + ; + +optionalPositionalFormalParameters + : OB defaultFormalParameter (C defaultFormalParameter)* C? CB + ; + +optionalPositionalParameterTypes + : OB normalParameterTypes C? CB + ; + +parameterTypeList + : OP CP + | OP normalParameterTypes C optionalParameterTypes CP + | OP normalParameterTypes C? CP + | OP optionalParameterTypes CP + ; + +partDeclaration + : partHeader (metadata topLevelDeclaration)* + ; + +partDirective + : metadata PART_ uri SC + ; + +partHeader + : metadata PART_ OF_ (dottedIdentifierList | uri) SC + ; + +postfixExpression + : assignableExpression postfixOperator + | primary selector* + ; + +postfixOperator + : incrementOperator + ; + +prefixOperator + : minusOperator + | negationOperator + | tildeOperator + ; + +primary + : thisExpression + | SUPER_ unconditionalAssignableSelector + | SUPER_ argumentPart + | functionExpression + | literal + | identifier + | newExpression + | constObjectExpression + | constructorInvocation + | OP expr CP + ; + +qualifiedName + : typeIdentifier D identifier + | typeIdentifier D typeIdentifier D identifier + ; + +redirectingFactoryConstructorSignature + : CONST_? FACTORY_ constructorName formalParameterList EQ constructorDesignation + ; + +redirection + : CO THIS_ (D identifier)? arguments + ; + +relationalExpression + : bitwiseOrExpression (typeTest | typeCast | relationalOperator bitwiseOrExpression)? + | SUPER_ relationalOperator bitwiseOrExpression + ; + +relationalOperator + : GT EQ + | GT + | LTE + | LT + ; + +reserved_word + : ASSERT_ + | BREAK_ + | CASE_ + | CATCH_ + | CLASS_ + | CONST_ + | CONTINUE_ + | DEFAULT_ + | DO_ + | ELSE_ + | ENUM_ + | EXTENDS_ + | FALSE_ + | FINAL_ + | FINALLY_ + | FOR_ + | IF_ + | IN_ + | IS_ + | NEW_ + | NULL_ + | RETHROW_ + | RETURN_ + | SUPER_ + | SWITCH_ + | THIS_ + | THROW_ + | TRUE_ + | TRY_ + | VAR_ + | VOID_ + | WHILE_ + | WITH_ + ; + +rethrowStatement + : RETHROW_ SC + ; + +returnStatement + : RETURN_ expr? SC + ; + +selector + : NOT + | assignableSelector + | argumentPart + ; + +setOrMapLiteral + : CONST_? typeArguments? OBC elements? CBC + ; + +setterSignature + : type? SET_ identifier formalParameterList + ; + +shiftExpression + : additiveExpression (shiftOperator additiveExpression)* + | SUPER_ ( shiftOperator additiveExpression)+ + ; + +shiftOperator + : LTLT + | GT GT GT + | GT GT + ; + +simpleFormalParameter + : declaredIdentifier + | COVARIANT_? identifier + ; + +singleLineString + : SingleLineString + ; + +spreadElement + : (DDD | DDDQ) expr + ; + +statement + : label* nonLabelledStatement + ; + +statements + : statement* + ; + +staticFinalDeclaration + : identifier EQ expr + ; + +staticFinalDeclarationList + : staticFinalDeclaration (C staticFinalDeclaration)* + ; + +stringLiteral + : (multilineString | singleLineString)+ + ; + +superclass + : EXTENDS_ typeNotVoid mixins? + | mixins + ; + +switchCase + : label* CASE_ expr CO statements + ; + +switchStatement + : SWITCH_ OP expr CP OBC switchCase* defaultCase? CBC + ; + +symbolLiteral + : PO (identifier ( D identifier)* | operator | VOID_) + ; + +thisExpression + : THIS_ + ; + +throwExpression + : THROW_ expr + ; + +throwExpressionWithoutCascade + : THROW_ expressionWithoutCascade + ; + +tildeOperator + : SQUIG + ; + +topLevelDeclaration + : classDeclaration + | mixinDeclaration + | extensionDeclaration + | enumType + | typeAlias + | EXTERNAL_ functionSignature SC + | EXTERNAL_ getterSignature SC + | EXTERNAL_ setterSignature SC + | functionSignature functionBody + | getterSignature functionBody + | setterSignature functionBody + | ( FINAL_ | CONST_) type? staticFinalDeclarationList SC + | LATE_ FINAL_ type? initializedIdentifierList SC + | LATE_? varOrType initializedIdentifierList SC + ; + +tryStatement + : TRY_ block (onPart+ finallyPart? | finallyPart) + ; + +type + : functionType QU? + | typeNotFunction + ; + +typeAlias + : TYPEDEF_ typeIdentifier typeParameters? EQ type SC + | TYPEDEF_ functionTypeAlias + ; + +typeArguments + : LT typeList GT + ; + +typeCast + : asOperator typeNotVoid + ; + +typedIdentifier + : type identifier + ; + +typeIdentifier + : IDENTIFIER + | ASYNC_ + | HIDE_ + | OF_ + | ON_ + | SHOW_ + | SYNC_ + | AWAIT_ + | YIELD_ + | DYNAMIC_ + | NATIVE_ + | FUNCTION_ + | BASE_ + | SEALED_ + ; + +typeList + : type (C type)* + ; + +typeName + : typeIdentifier (D typeIdentifier)? + ; + +typeNotFunction + : VOID_ + | typeNotVoidNotFunction + ; + +typeNotVoid + : functionType QU? + | typeNotVoidNotFunction + ; + +typeNotVoidList + : typeNotVoid (C typeNotVoid)* + ; + +typeNotVoidNotFunction + : typeName typeArguments? QU? + | FUNCTION_ QU? + ; + +typeNotVoidNotFunctionList + : typeNotVoidNotFunction (C typeNotVoidNotFunction)* + ; + +typeParameter + : metadata identifier (EXTENDS_ typeNotVoid)? + ; + +typeParameters + : LT typeParameter (C typeParameter)* GT + ; + +typeTest + : isOperator typeNotVoid + ; + +unaryExpression + : prefixOperator unaryExpression + | awaitExpression + | postfixExpression + | ( minusOperator | tildeOperator) SUPER_ + | incrementOperator assignableExpression + ; + +unconditionalAssignableSelector + : OB expr CB + | D identifier + ; + +uri + : stringLiteral + ; + +uriTest + : dottedIdentifierList (EE stringLiteral)? + ; + +varOrType + : VAR_ + | type + ; + +whileStatement + : WHILE_ OP expr CP statement + ; + +yieldEachStatement + : YIELD_ ST expr SC + ; + +yieldStatement + : YIELD_ expr SC + ; \ No newline at end of file diff --git a/chapi-ast-dart/src/main/java/chapi/ast/antlr/Dart2LexerBase.java b/chapi-ast-dart/src/main/java/chapi/ast/antlr/Dart2LexerBase.java new file mode 100644 index 00000000..480d3bc2 --- /dev/null +++ b/chapi-ast-dart/src/main/java/chapi/ast/antlr/Dart2LexerBase.java @@ -0,0 +1,13 @@ +package chapi.ast.antlr; + +import org.antlr.v4.runtime.*; + +public abstract class Dart2LexerBase extends Lexer { + protected Dart2LexerBase(CharStream input) { + super(input); + } + + protected boolean CheckNotOpenBrace() { + return _input.LA(1) != '{'; + } +} diff --git a/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAnalyser.kt b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAnalyser.kt new file mode 100644 index 00000000..0f7383ac --- /dev/null +++ b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAnalyser.kt @@ -0,0 +1,31 @@ +package chapi.ast.dartast + +import chapi.ast.antlr.Dart2Lexer +import chapi.ast.antlr.Dart2Parser +import chapi.domain.core.CodeContainer +import chapi.parser.Analyser +import org.antlr.v4.runtime.CharStreams +import org.antlr.v4.runtime.CommonTokenStream +import org.antlr.v4.runtime.tree.ParseTreeWalker + +open class DartAnalyser : Analyser { + override fun analysis(code: String, filePath: String): CodeContainer { + val context = parse(code).compilationUnit() + val listener = DartFullIdentListener(filePath = filePath) + ParseTreeWalker().walk(listener, context) + return listener.getNodeInfo() + } + + open fun identBasicInfo(code: String, filePath: String): CodeContainer { + val context = parse(code).compilationUnit() + val listener = DartBasicIdentListener(filePath = filePath) + ParseTreeWalker().walk(listener, context) + return listener.getNodeInfo() + } + + private fun parse(str: String): Dart2Parser = + CharStreams.fromString(str) + .let(::Dart2Lexer) + .let(::CommonTokenStream) + .let(::Dart2Parser) +} diff --git a/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAstListener.kt b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAstListener.kt new file mode 100644 index 00000000..eb229e70 --- /dev/null +++ b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartAstListener.kt @@ -0,0 +1,101 @@ +package chapi.ast.dartast + +import chapi.ast.antlr.Dart2Parser +import chapi.ast.antlr.Dart2ParserBaseListener +import chapi.domain.core.CodeAnnotation +import chapi.domain.core.CodePosition +import org.antlr.v4.runtime.ParserRuleContext + +/** + * Base class for Dart AST listeners providing common utility methods. + */ +open class DartAstListener : Dart2ParserBaseListener() { + + /** + * Build a CodePosition from a parser rule context + */ + protected fun buildPosition(ctx: ParserRuleContext): CodePosition { + val position = CodePosition() + position.StartLine = ctx.start.line + position.StartLinePosition = ctx.start.charPositionInLine + position.StopLine = ctx.stop?.line ?: ctx.start.line + position.StopLinePosition = ctx.stop?.charPositionInLine ?: ctx.start.charPositionInLine + return position + } + + /** + * Build a CodeAnnotation from a metadatum context + */ + protected fun buildAnnotation(ctx: Dart2Parser.MetadatumContext): CodeAnnotation { + val annotation = CodeAnnotation() + + // Get annotation name from identifier or qualified name + val identifier = ctx.identifier() + val qualifiedName = ctx.qualifiedName() + + annotation.Name = when { + identifier != null -> identifier.text + qualifiedName != null -> qualifiedName.text + else -> "" + } + + annotation.Position = buildPosition(ctx) + return annotation + } + + /** + * Build a list of CodeAnnotations from a metadata context + */ + protected fun buildAnnotations(ctx: Dart2Parser.MetadataContext?): List { + if (ctx == null) return emptyList() + return ctx.metadatum().map { buildAnnotation(it) } + } + + /** + * Extract type name from a type context + */ + protected fun getTypeName(ctx: Dart2Parser.TypeContext?): String { + if (ctx == null) return "" + return ctx.text + } + + /** + * Extract type name from typeNotVoid context + */ + protected fun getTypeNotVoidName(ctx: Dart2Parser.TypeNotVoidContext?): String { + if (ctx == null) return "" + return ctx.text + } + + /** + * Check if context has abstract modifier + */ + protected fun isAbstract(ctx: ParserRuleContext): Boolean { + return ctx.children?.any { + it.text == "abstract" + } ?: false + } + + /** + * Check if context has static modifier + */ + protected fun isStatic(ctx: ParserRuleContext): Boolean { + return ctx.children?.any { + it.text == "static" + } ?: false + } + + /** + * Extract identifier text safely + */ + protected fun getIdentifierText(ctx: Dart2Parser.IdentifierContext?): String { + return ctx?.text ?: "" + } + + /** + * Extract type identifier text safely + */ + protected fun getTypeIdentifierText(ctx: Dart2Parser.TypeIdentifierContext?): String { + return ctx?.text ?: "" + } +} diff --git a/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt new file mode 100644 index 00000000..4a099bda --- /dev/null +++ b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartBasicIdentListener.kt @@ -0,0 +1,591 @@ +package chapi.ast.dartast + +import chapi.ast.antlr.Dart2Parser +import chapi.domain.core.* + +/** + * Basic identifier listener for Dart code. + * Extracts basic structural information: imports, classes, mixins, extensions, enums, and function signatures. + */ +open class DartBasicIdentListener(private val filePath: String) : DartAstListener() { + + private var codeContainer: CodeContainer = CodeContainer( + FullName = filePath, + Language = "dart", + Kind = ContainerKind.SOURCE_FILE + ) + + private var classNodes: MutableList = mutableListOf() + protected var currentNode: CodeDataStruct = CodeDataStruct() + private var currentFunction: CodeFunction = CodeFunction() + protected var hasEnterClass: Boolean = false + + // Track library name if specified + override fun enterLibraryName(ctx: Dart2Parser.LibraryNameContext?) { + ctx?.dottedIdentifierList()?.let { + codeContainer.PackageName = it.text + codeContainer.DeclaredPackage = it.text + } + } + + // Handle library imports + override fun enterLibraryImport(ctx: Dart2Parser.LibraryImportContext?) { + val importSpec = ctx?.importSpecification() + val uri = importSpec?.configurableUri()?.uri()?.stringLiteral()?.text?.trim('"', '\'') ?: return + + val codeImport = CodeImport(Source = uri) + codeImport.PathSegments = uri.split("/") + + // Handle 'as' prefix + val asId = importSpec.identifier() + if (asId != null) { + codeImport.AsName = asId.text + } + + // Handle show/hide combinators + val combinators = importSpec.combinator() + for (combinator in combinators) { + val identifiers = combinator.identifierList()?.identifier()?.map { it.text } ?: continue + if (combinator.SHOW_() != null) { + codeImport.UsageName = identifiers + codeImport.Kind = ImportKind.NAMED + } else if (combinator.HIDE_() != null) { + // Store hidden identifiers in metadata + codeImport.Kind = ImportKind.NAMED + } + } + + if (codeImport.Kind == ImportKind.UNKNOWN) { + codeImport.Kind = ImportKind.WILDCARD + } + + codeContainer.Imports += codeImport + } + + // Handle library exports + override fun enterLibraryExport(ctx: Dart2Parser.LibraryExportContext?) { + val uri = ctx?.configurableUri()?.uri()?.stringLiteral()?.text?.trim('"', '\'') ?: return + + // Create an export entry and store in TopLevel scope + val codeExport = CodeExport( + Name = uri, + FromSource = uri, + Kind = ExportKind.RE_EXPORT_ALL + ) + + // Handle show/hide combinators for exports + ctx.combinator()?.forEach { combinator -> + val identifiers = combinator.identifierList()?.identifier()?.map { it.text } ?: return@forEach + if (combinator.SHOW_() != null) { + codeExport.Kind = ExportKind.RE_EXPORT_NAMED + codeExport.Specifiers = identifiers.map { name -> + ExportSpecifier(LocalName = name, ExportedName = name) + } + } + } + + ensureTopLevelScope() + codeContainer.TopLevel = codeContainer.TopLevel?.copy( + Exports = (codeContainer.TopLevel?.Exports ?: emptyList()) + codeExport + ) + } + + // Handle class declaration + override fun enterClassDeclaration(ctx: Dart2Parser.ClassDeclarationContext?) { + hasEnterClass = true + + val typeId = ctx?.typeIdentifier() ?: return + + // Determine class type based on modifiers (Dart 3 support) + val modifiers = ctx.classModifier() + val isAbstract = modifiers?.any { it.ABSTRACT_() != null } == true + val isSealed = modifiers?.any { it.SEALED_() != null } == true + val isBase = modifiers?.any { it.BASE_() != null } == true + val isInterface = modifiers?.any { it.INTERFACE_() != null } == true + val isFinal = modifiers?.any { it.FINAL_() != null } == true + val isMixinClass = modifiers?.any { it.MIXIN_() != null } == true + + val classType = when { + isSealed -> DataStructType.CLASS // sealed classes are still classes + isAbstract -> DataStructType.ABSTRACT_CLASS + isInterface -> DataStructType.INTERFACE + isMixinClass -> DataStructType.TRAIT // mixin class is similar to trait + else -> DataStructType.CLASS + } + + currentNode = CodeDataStruct( + NodeName = getTypeIdentifierText(typeId), + Type = classType, + Package = codeContainer.PackageName, + FilePath = filePath + ) + + // Store class modifiers as annotations for additional context + val modifierList = mutableListOf() + if (isSealed) modifierList.add("sealed") + if (isBase) modifierList.add("base") + if (isInterface) modifierList.add("interface") + if (isFinal) modifierList.add("final") + if (isAbstract) modifierList.add("abstract") + if (isMixinClass) modifierList.add("mixin") + + if (modifierList.isNotEmpty()) { + currentNode.Annotations = modifierList.map { + CodeAnnotation(Name = it) + } + } + + currentNode.Position = buildPosition(ctx) + + // Handle extends + ctx.superclass()?.let { superclass -> + superclass.typeNotVoid()?.let { + currentNode.Extend = it.text + } + // Handle mixins in superclass (e.g., extends Foo with Bar, Baz) + superclass.mixins()?.typeNotVoidList()?.typeNotVoid()?.let { mixins -> + currentNode.MultipleExtend = mixins.map { it.text } + } + } + + // Handle implements + ctx.interfaces()?.typeNotVoidList()?.typeNotVoid()?.let { interfaces -> + currentNode.Implements = interfaces.map { it.text } + } + } + + override fun exitClassDeclaration(ctx: Dart2Parser.ClassDeclarationContext?) { + hasEnterClass = false + if (currentNode.NodeName.isNotEmpty()) { + classNodes.add(currentNode) + } + currentNode = CodeDataStruct() + } + + // Handle mixin declaration + override fun enterMixinDeclaration(ctx: Dart2Parser.MixinDeclarationContext?) { + hasEnterClass = true + + val typeId = ctx?.typeIdentifier() ?: return + + // Check for Dart 3 mixin modifiers (base, sealed) + val isBase = ctx.BASE_() != null + val isSealed = ctx.SEALED_() != null + + currentNode = CodeDataStruct( + NodeName = getTypeIdentifierText(typeId), + Type = DataStructType.TRAIT, // Mixin is similar to trait + Package = codeContainer.PackageName, + FilePath = filePath + ) + + // Store mixin modifiers + val modifierList = mutableListOf("mixin") + if (isBase) modifierList.add("base") + if (isSealed) modifierList.add("sealed") + currentNode.Annotations = modifierList.map { CodeAnnotation(Name = it) } + + currentNode.Position = buildPosition(ctx) + + // Handle 'on' constraints (superclass constraints) + // In ANTLR, the grammar has: (ON_ typeNotVoidList)? + // typeNotVoidList returns a single context containing multiple typeNotVoid elements + ctx.typeNotVoidList()?.let { typeList -> + val constraints = typeList.typeNotVoid() + if (constraints.isNotEmpty()) { + currentNode.MultipleExtend = constraints.map { it.text } + } + } + + // Handle implements + ctx.interfaces()?.typeNotVoidList()?.typeNotVoid()?.let { interfaces -> + currentNode.Implements = interfaces.map { it.text } + } + } + + override fun exitMixinDeclaration(ctx: Dart2Parser.MixinDeclarationContext?) { + hasEnterClass = false + if (currentNode.NodeName.isNotEmpty()) { + classNodes.add(currentNode) + } + currentNode = CodeDataStruct() + } + + // Handle extension declaration + override fun enterExtensionDeclaration(ctx: Dart2Parser.ExtensionDeclarationContext?) { + hasEnterClass = true + + ctx ?: return + + val name = ctx.identifier()?.text ?: "anonymous" + currentNode = CodeDataStruct( + NodeName = name, + Type = DataStructType.OBJECT, // Extension is like an object extension + Package = codeContainer.PackageName, + FilePath = filePath + ) + currentNode.Position = buildPosition(ctx) + + // Extension target type + ctx.type()?.let { + currentNode.Extend = it.text + } + } + + override fun exitExtensionDeclaration(ctx: Dart2Parser.ExtensionDeclarationContext?) { + hasEnterClass = false + if (currentNode.NodeName.isNotEmpty()) { + classNodes.add(currentNode) + } + currentNode = CodeDataStruct() + } + + // Handle enum declaration + override fun enterEnumType(ctx: Dart2Parser.EnumTypeContext?) { + hasEnterClass = true + + val name = ctx?.identifier()?.text ?: return + currentNode = CodeDataStruct( + NodeName = name, + Type = DataStructType.ENUM, + Package = codeContainer.PackageName, + FilePath = filePath + ) + currentNode.Position = buildPosition(ctx) + + // Extract enum entries as fields + ctx.enumEntry()?.let { entries -> + currentNode.Fields = entries.map { entry -> + CodeField( + TypeKey = entry.identifier()?.text ?: "", + TypeType = name, + TypeValue = entry.identifier()?.text ?: "" + ) + } + } + } + + override fun exitEnumType(ctx: Dart2Parser.EnumTypeContext?) { + hasEnterClass = false + if (currentNode.NodeName.isNotEmpty()) { + classNodes.add(currentNode) + } + currentNode = CodeDataStruct() + } + + // Handle function signatures in class members + override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { + val funcSig = ctx?.functionSignature() + val getterSig = ctx?.getterSignature() + val setterSig = ctx?.setterSignature() + val constructorSig = ctx?.constructorSignature() + val factorySig = ctx?.factoryConstructorSignature() + val operatorSig = ctx?.operatorSignature() + + currentFunction = when { + constructorSig != null -> buildConstructorFunction(constructorSig, ctx) + factorySig != null -> buildFactoryConstructorFunction(factorySig, ctx) + funcSig != null -> buildFunction(funcSig, ctx) + getterSig != null -> buildGetterFunction(getterSig, ctx) + setterSig != null -> buildSetterFunction(setterSig, ctx) + operatorSig != null -> buildOperatorFunction(operatorSig, ctx) + else -> CodeFunction() + } + } + + override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { + if (hasEnterClass && currentFunction.Name.isNotEmpty()) { + currentNode.Functions += currentFunction + } + currentFunction = CodeFunction() + } + + private fun buildFunction(ctx: Dart2Parser.FunctionSignatureContext, parent: Dart2Parser.MethodSignatureContext): CodeFunction { + val func = CodeFunction( + Name = ctx.identifier()?.text ?: "", + ReturnType = ctx.type()?.text ?: "void", + Position = buildPosition(parent), + IsConstructor = false + ) + + // Parse parameters + ctx.formalParameterPart()?.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + // Check for static modifier + if (parent.STATIC_() != null) { + func.Modifiers += "static" + } + + return func + } + + private fun buildConstructorFunction(ctx: Dart2Parser.ConstructorSignatureContext, parent: Dart2Parser.MethodSignatureContext): CodeFunction { + val name = ctx.constructorName()?.typeIdentifier()?.text ?: "" + val namedPart = ctx.constructorName()?.identifier()?.text + + val func = CodeFunction( + Name = if (namedPart != null) "$name.$namedPart" else name, + ReturnType = name, + Position = buildPosition(parent), + IsConstructor = true + ) + + ctx.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + return func + } + + private fun buildFactoryConstructorFunction(ctx: Dart2Parser.FactoryConstructorSignatureContext, parent: Dart2Parser.MethodSignatureContext): CodeFunction { + val name = ctx.constructorName()?.typeIdentifier()?.text ?: "" + val namedPart = ctx.constructorName()?.identifier()?.text + + val func = CodeFunction( + Name = if (namedPart != null) "$name.$namedPart" else name, + ReturnType = name, + Position = buildPosition(parent), + IsConstructor = true + ) + func.Modifiers += "factory" + + if (ctx.CONST_() != null) { + func.Modifiers += "const" + } + + ctx.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + return func + } + + private fun buildGetterFunction(ctx: Dart2Parser.GetterSignatureContext, parent: Dart2Parser.MethodSignatureContext): CodeFunction { + return CodeFunction( + Name = "get ${ctx.identifier()?.text ?: ""}", + ReturnType = ctx.type()?.text ?: "dynamic", + Position = buildPosition(parent), + IsConstructor = false + ).also { + it.Modifiers += "getter" + if (parent.STATIC_() != null) { + it.Modifiers += "static" + } + } + } + + private fun buildSetterFunction(ctx: Dart2Parser.SetterSignatureContext, parent: Dart2Parser.MethodSignatureContext): CodeFunction { + val func = CodeFunction( + Name = "set ${ctx.identifier()?.text ?: ""}", + ReturnType = "void", + Position = buildPosition(parent), + IsConstructor = false + ) + func.Modifiers += "setter" + if (parent.STATIC_() != null) { + func.Modifiers += "static" + } + + ctx.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + return func + } + + private fun buildOperatorFunction(ctx: Dart2Parser.OperatorSignatureContext, parent: Dart2Parser.MethodSignatureContext): CodeFunction { + val operatorSymbol = ctx.operator()?.text ?: "" + val func = CodeFunction( + Name = "operator $operatorSymbol", + ReturnType = ctx.type()?.text ?: "dynamic", + Position = buildPosition(parent), + IsConstructor = false + ) + func.Modifiers += "operator" + + ctx.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + return func + } + + private fun parseParameters(ctx: Dart2Parser.FormalParameterListContext): List { + val params = mutableListOf() + + // Normal parameters + ctx.normalFormalParameters()?.normalFormalParameter()?.forEach { param -> + parseNormalParameter(param)?.let { params.add(it) } + } + + // Optional positional parameters + ctx.optionalOrNamedFormalParameters()?.optionalPositionalFormalParameters() + ?.defaultFormalParameter()?.forEach { param -> + param.normalFormalParameter()?.let { + parseNormalParameter(it)?.let { p -> params.add(p) } + } + } + + // Named parameters + ctx.optionalOrNamedFormalParameters()?.namedFormalParameters() + ?.defaultNamedParameter()?.forEach { param -> + param.normalFormalParameterNoMetadata()?.let { + parseNormalParameterNoMetadata(it)?.let { p -> params.add(p) } + } + } + + return params + } + + private fun parseNormalParameter(ctx: Dart2Parser.NormalFormalParameterContext): CodeProperty? { + return parseNormalParameterNoMetadata(ctx.normalFormalParameterNoMetadata()) + } + + private fun parseNormalParameterNoMetadata(ctx: Dart2Parser.NormalFormalParameterNoMetadataContext?): CodeProperty? { + if (ctx == null) return null + + // Simple formal parameter (type identifier) + ctx.simpleFormalParameter()?.let { simple -> + simple.declaredIdentifier()?.let { declared -> + return CodeProperty( + TypeType = declared.finalConstVarOrType()?.text ?: "dynamic", + TypeValue = declared.identifier()?.text ?: "" + ) + } + simple.identifier()?.let { id -> + return CodeProperty( + TypeType = "dynamic", + TypeValue = id.text + ) + } + } + + // Function formal parameter + ctx.functionFormalParameter()?.let { funcParam -> + return CodeProperty( + TypeType = funcParam.type()?.text ?: "Function", + TypeValue = funcParam.identifier()?.text ?: "" + ) + } + + // Field formal parameter (this.xxx) + ctx.fieldFormalParameter()?.let { fieldParam -> + return CodeProperty( + TypeType = fieldParam.finalConstVarOrType()?.text ?: "dynamic", + TypeValue = fieldParam.identifier()?.text ?: "" + ) + } + + return null + } + + // Handle top-level function declaration + override fun enterTopLevelDeclaration(ctx: Dart2Parser.TopLevelDeclarationContext?) { + if (hasEnterClass) return + + // Handle top-level function + ctx?.functionSignature()?.let { funcSig -> + val func = CodeFunction( + Name = funcSig.identifier()?.text ?: "", + ReturnType = funcSig.type()?.text ?: "void", + Position = buildPosition(ctx), + IsConstructor = false + ) + + funcSig.formalParameterPart()?.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + // Store in top-level scope + ensureTopLevelScope() + codeContainer.TopLevel = codeContainer.TopLevel?.copy( + Functions = (codeContainer.TopLevel?.Functions ?: emptyList()) + func + ) + } + + // Handle top-level getter + ctx?.getterSignature()?.let { getterSig -> + val func = CodeFunction( + Name = "get ${getterSig.identifier()?.text ?: ""}", + ReturnType = getterSig.type()?.text ?: "dynamic", + Position = buildPosition(ctx), + IsConstructor = false + ) + func.Modifiers += "getter" + + ensureTopLevelScope() + codeContainer.TopLevel = codeContainer.TopLevel?.copy( + Functions = (codeContainer.TopLevel?.Functions ?: emptyList()) + func + ) + } + + // Handle top-level setter + ctx?.setterSignature()?.let { setterSig -> + val func = CodeFunction( + Name = "set ${setterSig.identifier()?.text ?: ""}", + ReturnType = "void", + Position = buildPosition(ctx), + IsConstructor = false + ) + func.Modifiers += "setter" + + setterSig.formalParameterList()?.let { params -> + func.Parameters = parseParameters(params) + } + + ensureTopLevelScope() + codeContainer.TopLevel = codeContainer.TopLevel?.copy( + Functions = (codeContainer.TopLevel?.Functions ?: emptyList()) + func + ) + } + + // Handle top-level variable declarations + ctx?.initializedIdentifierList()?.let { idList -> + idList.initializedIdentifier()?.forEach { initId -> + val field = CodeField( + TypeKey = initId.identifier()?.text ?: "", + TypeType = ctx.varOrType()?.type()?.text ?: ctx.type()?.text ?: "var", + TypeValue = "" + ) + + ensureTopLevelScope() + codeContainer.TopLevel = codeContainer.TopLevel?.copy( + Fields = (codeContainer.TopLevel?.Fields ?: emptyList()) + field + ) + } + } + } + + private fun ensureTopLevelScope() { + if (codeContainer.TopLevel == null) { + codeContainer.TopLevel = TopLevelScope() + } + } + + // Handle typedef + override fun enterTypeAlias(ctx: Dart2Parser.TypeAliasContext?) { + ctx?.typeIdentifier()?.let { typeId -> + val typedef = CodeDataStruct( + NodeName = getTypeIdentifierText(typeId), + Type = DataStructType.OBJECT, // typedef is like a type alias + Package = codeContainer.PackageName, + FilePath = filePath + ) + typedef.Position = buildPosition(ctx) + + // Store the aliased type + ctx.type()?.let { + typedef.Extend = it.text + } + + classNodes.add(typedef) + } + } + + fun getNodeInfo(): CodeContainer { + codeContainer.DataStructures = classNodes + return codeContainer + } +} diff --git a/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt new file mode 100644 index 00000000..2e368a74 --- /dev/null +++ b/chapi-ast-dart/src/main/kotlin/chapi/ast/dartast/DartFullIdentListener.kt @@ -0,0 +1,288 @@ +package chapi.ast.dartast + +import chapi.ast.antlr.Dart2Parser +import chapi.domain.core.* + +/** + * Full identifier listener for Dart code. + * Extends basic listener with method call analysis, expression handling, and field extraction. + */ +open class DartFullIdentListener(filePath: String) : DartBasicIdentListener(filePath) { + + private var currentFunctionCalls: MutableList = mutableListOf() + private var isInFunction: Boolean = false + private var localVariables: MutableMap = mutableMapOf() + + override fun enterMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { + super.enterMethodSignature(ctx) + // Don't set isInFunction here - wait for function body + } + + override fun exitMethodSignature(ctx: Dart2Parser.MethodSignatureContext?) { + super.exitMethodSignature(ctx) + } + + override fun enterFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { + isInFunction = true + currentFunctionCalls.clear() + localVariables.clear() + } + + override fun exitFunctionBody(ctx: Dart2Parser.FunctionBodyContext?) { + // Function calls collected during body processing are stored in currentFunctionCalls + // They can be attached to the current function if needed + isInFunction = false + } + + // Handle class member declarations for fields + override fun enterClassMemberDeclaration(ctx: Dart2Parser.ClassMemberDeclarationContext?) { + val declaration = ctx?.declaration() ?: return + + // Extract field declarations + extractFieldsFromDeclaration(declaration) + } + + private fun extractFieldsFromDeclaration(ctx: Dart2Parser.DeclarationContext) { + // Handle various field declaration patterns + + // Static const fields + ctx.staticFinalDeclarationList()?.staticFinalDeclaration()?.forEach { decl -> + val fieldName = decl.identifier()?.text ?: return@forEach + val fieldType = ctx.type()?.text ?: "var" + + addFieldToCurrentClass(fieldName, fieldType, isStatic = true, isFinal = true) + } + + // Initialized identifier lists (regular fields) + ctx.initializedIdentifierList()?.initializedIdentifier()?.forEach { initId -> + val fieldName = initId.identifier()?.text ?: return@forEach + val fieldType = extractTypeFromDeclaration(ctx) + + val isStatic = ctx.STATIC_() != null + val isLate = ctx.LATE_() != null + val isFinal = ctx.FINAL_() != null + + addFieldToCurrentClass(fieldName, fieldType, isStatic, isFinal, isLate) + } + } + + private fun extractTypeFromDeclaration(ctx: Dart2Parser.DeclarationContext): String { + return ctx.varOrType()?.type()?.text + ?: ctx.varOrType()?.VAR_()?.text + ?: ctx.type()?.text + ?: "var" + } + + /** + * Add a field to the current class being parsed. + * Creates a CodeField and adds it to the current node's fields. + */ + protected fun addFieldToCurrentClass( + name: String, + type: String, + isStatic: Boolean = false, + isFinal: Boolean = false, + isLate: Boolean = false + ) { + if (!hasEnterClass) return + + val modifiers = mutableListOf() + if (isStatic) modifiers.add("static") + if (isFinal) modifiers.add("final") + if (isLate) modifiers.add("late") + + val field = CodeField( + TypeKey = name, + TypeType = type, + TypeValue = "", + Modifiers = modifiers + ) + + currentNode.Fields += field + } + + // Handle new expressions + override fun enterNewExpression(ctx: Dart2Parser.NewExpressionContext?) { + if (!isInFunction) return + + val constructorDesignation = ctx?.constructorDesignation() ?: return + val call = CodeCall( + NodeName = constructorDesignation.text, + Type = CallType.CREATOR + ) + call.Position = buildPosition(ctx) + + ctx.arguments()?.argumentList()?.let { argList -> + call.Parameters = parseCallArguments(argList) + } + + currentFunctionCalls.add(call) + } + + // Handle const object expressions + override fun enterConstObjectExpression(ctx: Dart2Parser.ConstObjectExpressionContext?) { + if (!isInFunction) return + + val constructorDesignation = ctx?.constructorDesignation() ?: return + val call = CodeCall( + NodeName = constructorDesignation.text, + Type = CallType.CREATOR + ) + call.Position = buildPosition(ctx) + + ctx.arguments()?.argumentList()?.let { argList -> + call.Parameters = parseCallArguments(argList) + } + + currentFunctionCalls.add(call) + } + + // Handle constructor invocations + override fun enterConstructorInvocation(ctx: Dart2Parser.ConstructorInvocationContext?) { + if (!isInFunction) return + + val typeName = ctx?.typeName()?.text ?: return + val identifier = ctx.identifier()?.text ?: "" + + val call = CodeCall( + NodeName = if (identifier.isNotEmpty()) "$typeName.$identifier" else typeName, + Type = CallType.CREATOR + ) + call.Position = buildPosition(ctx) + + ctx.arguments()?.argumentList()?.let { argList -> + call.Parameters = parseCallArguments(argList) + } + + currentFunctionCalls.add(call) + } + + // Handle postfix expressions (method calls like obj.method()) + override fun enterPostfixExpression(ctx: Dart2Parser.PostfixExpressionContext?) { + if (!isInFunction) return + + // Extract method call chain + extractMethodCallChain(ctx) + } + + private fun extractMethodCallChain(ctx: Dart2Parser.PostfixExpressionContext?) { + if (ctx == null) return + + val primary = ctx.primary() + val selectors = ctx.selector() + + if (primary == null || selectors.isNullOrEmpty()) return + + var currentTarget = primary.text + + for (selector in selectors) { + // First update target if there's an identifier selector + selector.assignableSelector()?.let { assignable -> + assignable.identifier()?.let { id -> + currentTarget = "$currentTarget.${id.text}" + } + } + + // Check if this selector includes arguments (function call) + selector.argumentPart()?.let { argPart -> + val call = CodeCall( + NodeName = currentTarget, + Type = CallType.FUNCTION + ) + call.Position = buildPosition(selector) + + argPart.arguments()?.argumentList()?.let { argList -> + call.Parameters = parseCallArguments(argList) + } + + currentFunctionCalls.add(call) + } + } + } + + private fun parseCallArguments(ctx: Dart2Parser.ArgumentListContext): List { + val params = mutableListOf() + + // Expression list + ctx.expressionList()?.expr()?.forEach { expr -> + params.add(CodeProperty(TypeType = "dynamic", TypeValue = expr.text)) + } + + // Named arguments + ctx.namedArgument()?.forEach { named -> + val name = named.label()?.identifier()?.text ?: "" + val value = named.expr()?.text ?: "" + params.add(CodeProperty(TypeType = value, TypeValue = name)) + } + + return params + } + + // Handle local variable declarations + override fun enterLocalVariableDeclaration(ctx: Dart2Parser.LocalVariableDeclarationContext?) { + ctx?.initializedVariableDeclaration()?.let { varDecl -> + val declaredId = varDecl.declaredIdentifier() + val varName = declaredId?.identifier()?.text ?: return + val varType = declaredId.finalConstVarOrType()?.text ?: "var" + + localVariables[varName] = varType + } + } + + // Handle cascade expressions + override fun enterCascade(ctx: Dart2Parser.CascadeContext?) { + if (!isInFunction) return + if (ctx == null) return + + // Cascade sections represent method calls on the same target + val cascadeSection = ctx.cascadeSection() + if (cascadeSection != null) { + extractCascadeCall(cascadeSection) + } + } + + private fun extractCascadeCall(ctx: Dart2Parser.CascadeSectionContext) { + val selector = ctx.cascadeSelector() ?: return + + selector.identifier()?.let { id -> + val call = CodeCall( + NodeName = id.text, + Type = CallType.FUNCTION + ) + call.Position = buildPosition(ctx) + currentFunctionCalls.add(call) + } + } + + // Handle throw expressions + override fun enterThrowExpression(ctx: Dart2Parser.ThrowExpressionContext?) { + if (!isInFunction) return + ctx ?: return + + val call = CodeCall( + NodeName = "throw", + Type = CallType.FUNCTION + ) + call.Position = buildPosition(ctx) + currentFunctionCalls.add(call) + } + + // Handle super calls + override fun enterInitializerListEntry(ctx: Dart2Parser.InitializerListEntryContext?) { + // Super constructor calls in initializer lists + if (ctx?.SUPER_() != null) { + val identifier = ctx.identifier()?.text + val call = CodeCall( + NodeName = if (identifier != null) "super.$identifier" else "super", + Type = CallType.SUPER + ) + call.Position = buildPosition(ctx) + currentFunctionCalls.add(call) + } + } + + /** + * Get the function calls collected during parsing. + */ + fun getFunctionCalls(): List = currentFunctionCalls.toList() +} diff --git a/chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt b/chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt new file mode 100644 index 00000000..caad2558 --- /dev/null +++ b/chapi-ast-dart/src/test/kotlin/chapi/ast/dartast/DartAnalyserTest.kt @@ -0,0 +1,698 @@ +package chapi.ast.dartast + +import chapi.domain.core.DataStructType +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +internal class DartAnalyserTest { + + @Test + fun shouldParseHelloWorld() { + val code = """ + void main() { + print('Hello, World!'); + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "hello_world.dart") + + assertEquals("dart", codeContainer.Language) + assertEquals("hello_world.dart", codeContainer.FullName) + assertNotNull(codeContainer.TopLevel) + assertEquals(1, codeContainer.TopLevel?.Functions?.size) + assertEquals("main", codeContainer.TopLevel?.Functions?.get(0)?.Name) + } + + @Test + fun shouldParseLibraryName() { + val code = """ + library my_library; + + void foo() {} + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "my_lib.dart") + + assertEquals("my_library", codeContainer.PackageName) + assertEquals("my_library", codeContainer.DeclaredPackage) + } + + @Test + fun shouldParseImports() { + val code = """ + import 'dart:async'; + import 'dart:collection' show LinkedHashMap; + import 'package:flutter/material.dart' as material; + + void main() {} + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "imports.dart") + + assertEquals(3, codeContainer.Imports.size) + assertEquals("dart:async", codeContainer.Imports[0].Source) + assertEquals("dart:collection", codeContainer.Imports[1].Source) + assertEquals("material", codeContainer.Imports[2].AsName) + } + + @Test + fun shouldParseSimpleClass() { + val code = """ + class Person { + String name; + int age; + + String greet() { + return 'Hello, my name is name'; + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "person.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val personClass = codeContainer.DataStructures[0] + assertEquals("Person", personClass.NodeName) + assertEquals(DataStructType.CLASS, personClass.Type) + assertTrue(personClass.Functions.isNotEmpty()) + assertEquals("greet", personClass.Functions[0].Name) + } + + @Test + fun shouldParseClassWithInheritance() { + val code = """ + class Animal { + void makeSound() {} + } + + class Dog extends Animal { + @override + void makeSound() { + print('Woof!'); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "animal.dart") + + assertEquals(2, codeContainer.DataStructures.size) + + val dogClass = codeContainer.DataStructures[1] + assertEquals("Dog", dogClass.NodeName) + assertEquals("Animal", dogClass.Extend) + } + + @Test + fun shouldParseClassWithImplements() { + val code = """ + abstract class Comparable { + int compareTo(T other); + } + + class Person implements Comparable { + String name; + + @override + int compareTo(Person other) { + return name.compareTo(other.name); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "comparable.dart") + + val personClass = codeContainer.DataStructures.find { it.NodeName == "Person" } + assertNotNull(personClass) + assertEquals(1, personClass!!.Implements.size) + assertEquals("Comparable", personClass.Implements[0]) + } + + @Test + fun shouldParseAbstractClass() { + val code = """ + abstract class Animal { + String get species; + void makeSound(); + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "abstract.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val animalClass = codeContainer.DataStructures[0] + assertEquals("Animal", animalClass.NodeName) + assertEquals(DataStructType.ABSTRACT_CLASS, animalClass.Type) + } + + @Test + fun shouldParseMixin() { + val code = """ + mixin Printable { + void printInfo() { + print(toString()); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "mixin.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val mixin = codeContainer.DataStructures[0] + assertEquals("Printable", mixin.NodeName) + assertEquals(DataStructType.TRAIT, mixin.Type) + } + + @Test + fun shouldParseEnum() { + val code = """ + enum Status { + pending, + active, + completed, + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "enum.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val enumType = codeContainer.DataStructures[0] + assertEquals("Status", enumType.NodeName) + assertEquals(DataStructType.ENUM, enumType.Type) + assertEquals(3, enumType.Fields.size) + } + + @Test + fun shouldParseExtension() { + val code = """ + extension StringExtension on String { + String capitalize() { + if (isEmpty) return this; + return this[0].toUpperCase() + substring(1); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "extension.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val extension = codeContainer.DataStructures[0] + assertEquals("StringExtension", extension.NodeName) + assertEquals("String", extension.Extend) + } + + @Test + fun shouldParseConstructors() { + val code = """ + class Person { + String name; + + Person(this.name); + + Person.anonymous() : name = 'Unknown'; + + factory Person.fromJson(Map json) { + return Person(json['name']); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "constructors.dart") + + val personClass = codeContainer.DataStructures[0] + val constructors = personClass.Functions.filter { it.IsConstructor } + + assertTrue(constructors.isNotEmpty()) + } + + @Test + fun shouldParseGetterAndSetter() { + val code = """ + class Rectangle { + double _width; + double _height; + + double get area => _width * _height; + + set width(double value) { + _width = value; + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "getset.dart") + + val rectClass = codeContainer.DataStructures[0] + val getter = rectClass.Functions.find { it.Name.startsWith("get ") } + val setter = rectClass.Functions.find { it.Name.startsWith("set ") } + + assertNotNull(getter) + assertNotNull(setter) + } + + @Test + fun shouldParseTopLevelFunction() { + val code = """ + void greet(String name) { + print('Hello, name!'); + } + + int add(int a, int b) => a + b; + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "functions.dart") + + assertNotNull(codeContainer.TopLevel) + assertEquals(2, codeContainer.TopLevel?.Functions?.size) + } + + @Test + fun shouldParseTopLevelVariables() { + val code = """ + const String appName = 'MyApp'; + final int maxRetries = 3; + var counter = 0; + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "variables.dart") + + assertNotNull(codeContainer.TopLevel) + assertTrue(codeContainer.TopLevel?.Fields?.isNotEmpty() == true) + } + + @Test + fun shouldParseComplexFile() { + val code = this::class.java.getResource("/grammar/class_example.dart")?.readText() + if (code == null) { + // Skip if resource not found + return + } + + val codeContainer = DartAnalyser().analysis(code, "class_example.dart") + + assertEquals("example", codeContainer.PackageName) + assertTrue(codeContainer.Imports.isNotEmpty()) + assertTrue(codeContainer.DataStructures.isNotEmpty()) + } + + @Test + fun shouldParseBasicInfo() { + val code = """ + class Simple { + void method() {} + } + """.trimIndent() + + val codeContainer = DartAnalyser().identBasicInfo(code, "simple.dart") + + assertEquals(1, codeContainer.DataStructures.size) + assertEquals("Simple", codeContainer.DataStructures[0].NodeName) + } + + // Dart 3 tests + @Test + fun shouldParseSealedClass() { + val code = """ + sealed class Shape {} + + class Circle extends Shape { + double radius; + } + + class Square extends Shape { + double side; + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "sealed.dart") + + assertEquals(3, codeContainer.DataStructures.size) + val shapeClass = codeContainer.DataStructures[0] + assertEquals("Shape", shapeClass.NodeName) + assertTrue(shapeClass.Annotations.any { it.Name == "sealed" }) + } + + @Test + fun shouldParseBaseClass() { + val code = """ + base class Vehicle { + void move() {} + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "base.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val vehicleClass = codeContainer.DataStructures[0] + assertEquals("Vehicle", vehicleClass.NodeName) + assertTrue(vehicleClass.Annotations.any { it.Name == "base" }) + } + + @Test + fun shouldParseInterfaceClass() { + val code = """ + interface class Flyable { + void fly(); + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "interface.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val flyableClass = codeContainer.DataStructures[0] + assertEquals("Flyable", flyableClass.NodeName) + assertEquals(DataStructType.INTERFACE, flyableClass.Type) + } + + @Test + fun shouldParseFinalClass() { + val code = """ + final class Config { + final String name; + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "final.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val configClass = codeContainer.DataStructures[0] + assertEquals("Config", configClass.NodeName) + assertTrue(configClass.Annotations.any { it.Name == "final" }) + } + + @Test + fun shouldParseMixinClass() { + val code = """ + mixin class Both { + void doSomething() {} + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "mixinclass.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val bothClass = codeContainer.DataStructures[0] + assertEquals("Both", bothClass.NodeName) + assertEquals(DataStructType.TRAIT, bothClass.Type) + } + + @Test + fun shouldParseBaseMixin() { + val code = """ + base mixin Logger { + void log(String message) { + print(message); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "basemixin.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val loggerMixin = codeContainer.DataStructures[0] + assertEquals("Logger", loggerMixin.NodeName) + assertEquals(DataStructType.TRAIT, loggerMixin.Type) + assertTrue(loggerMixin.Annotations.any { it.Name == "base" }) + } + + // Additional coverage tests + @Test + fun shouldParseExports() { + val code = """ + library mylib; + + export 'src/models.dart'; + export 'src/utils.dart' show Helper, Utils; + + void main() {} + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "exports.dart") + + assertNotNull(codeContainer.TopLevel) + val exports = codeContainer.TopLevel?.Exports ?: emptyList() + assertEquals(2, exports.size) + assertEquals("src/models.dart", exports[0].Name) + assertEquals("src/utils.dart", exports[1].Name) + } + + @Test + fun shouldParseClassFields() { + val code = """ + class User { + static const int maxAge = 120; + static final String defaultName = 'Unknown'; + late String name; + final int age; + String? nickname; + + User(this.name, this.age); + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "fields.dart") + + val userClass = codeContainer.DataStructures[0] + assertEquals("User", userClass.NodeName) + assertTrue(userClass.Fields.isNotEmpty()) + } + + @Test + fun shouldParseMethodParameters() { + val code = """ + class Calculator { + int add(int a, int b) => a + b; + + void greet(String name, {int? age, bool isVip = false}) { + print('Hello name'); + } + + void log([String message = 'default']) { + print(message); + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "params.dart") + + val calcClass = codeContainer.DataStructures[0] + assertEquals("Calculator", calcClass.NodeName) + assertEquals(3, calcClass.Functions.size) + + val addMethod = calcClass.Functions.find { it.Name == "add" } + assertNotNull(addMethod) + assertEquals(2, addMethod!!.Parameters.size) + } + + @Test + fun shouldParseTypedef() { + val code = """ + typedef IntCallback = void Function(int value); + typedef JsonMap = Map; + typedef Compare = int Function(T a, T b); + + void main() {} + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "typedef.dart") + + // Typedefs are parsed as data structures + assertTrue(codeContainer.DataStructures.size >= 3) + } + + @Test + fun shouldParseOperatorOverloading() { + val code = """ + class Vector { + final int x, y; + + Vector(this.x, this.y); + + Vector operator +(Vector other) { + return Vector(x + other.x, y + other.y); + } + + bool operator ==(Object other) { + if (other is Vector) { + return x == other.x && y == other.y; + } + return false; + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "operator.dart") + + val vectorClass = codeContainer.DataStructures[0] + assertEquals("Vector", vectorClass.NodeName) + + val operators = vectorClass.Functions.filter { it.Name.startsWith("operator") } + assertTrue(operators.isNotEmpty()) + } + + @Test + fun shouldParseMixinWithConstraints() { + val code = """ + mixin Musical on Performer { + void playInstrument() {} + } + + mixin Theatrical on Performer implements Actor { + void act() {} + } + + class Performer {} + abstract class Actor {} + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "mixin_constraints.dart") + + val musicalMixin = codeContainer.DataStructures.find { it.NodeName == "Musical" } + assertNotNull(musicalMixin) + assertEquals(DataStructType.TRAIT, musicalMixin!!.Type) + assertTrue(musicalMixin.MultipleExtend.isNotEmpty()) + } + + @Test + fun shouldParseClassWithMixins() { + val code = """ + mixin A {} + mixin B {} + class Base {} + + class Child extends Base with A, B { + void method() {} + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "with_mixins.dart") + + val childClass = codeContainer.DataStructures.find { it.NodeName == "Child" } + assertNotNull(childClass) + assertEquals("Base", childClass!!.Extend) + assertTrue(childClass.MultipleExtend.isNotEmpty()) + } + + @Test + fun shouldParseImportHide() { + val code = """ + import 'dart:math' hide Random; + + void main() {} + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "import_hide.dart") + + assertEquals(1, codeContainer.Imports.size) + assertEquals("dart:math", codeContainer.Imports[0].Source) + } + + @Test + fun shouldParseStaticMethods() { + val code = """ + class Utils { + static void log(String message) { + print(message); + } + + static int parse(String value) => int.parse(value); + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "static.dart") + + val utilsClass = codeContainer.DataStructures[0] + assertEquals(2, utilsClass.Functions.size) + assertTrue(utilsClass.Functions.all { it.Modifiers.contains("static") }) + } + + @Test + fun shouldParseAsyncMethods() { + val code = """ + class ApiService { + Future fetchData() async { + return 'data'; + } + + Stream countStream() async* { + for (var i = 0; i < 10; i++) { + yield i; + } + } + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "async.dart") + + val serviceClass = codeContainer.DataStructures[0] + assertEquals(2, serviceClass.Functions.size) + } + + @Test + fun shouldParseGenericClass() { + val code = """ + class Box { + T? value; + + Box([this.value]); + + void set(T newValue) { + value = newValue; + } + + T? get() => value; + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "generic.dart") + + val boxClass = codeContainer.DataStructures[0] + assertEquals("Box", boxClass.NodeName) + assertTrue(boxClass.Functions.isNotEmpty()) + } + + @Test + fun shouldParseAnonymousExtension() { + val code = """ + extension on int { + bool get isEven => this % 2 == 0; + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "anon_ext.dart") + + assertEquals(1, codeContainer.DataStructures.size) + val extension = codeContainer.DataStructures[0] + assertEquals("anonymous", extension.NodeName) + assertEquals("int", extension.Extend) + } + + @Test + fun shouldParseTopLevelGetter() { + val code = """ + String get appVersion => '1.0.0'; + + set appConfig(String value) { + print('Setting config'); + } + """.trimIndent() + + val codeContainer = DartAnalyser().analysis(code, "top_getter.dart") + + assertNotNull(codeContainer.TopLevel) + val functions = codeContainer.TopLevel?.Functions ?: emptyList() + assertTrue(functions.any { it.Name.startsWith("get ") }) + assertTrue(functions.any { it.Name.startsWith("set ") }) + } + + @Test + fun shouldParseComplexFileWithAssert() { + val code = this::class.java.getResource("/grammar/class_example.dart")?.readText() + assertNotNull(code, "Resource /grammar/class_example.dart not found") + + val codeContainer = DartAnalyser().analysis(code!!, "class_example.dart") + + assertEquals("example", codeContainer.PackageName) + assertTrue(codeContainer.Imports.isNotEmpty()) + assertTrue(codeContainer.DataStructures.isNotEmpty()) + } +} diff --git a/chapi-ast-dart/src/test/resources/grammar/class_example.dart b/chapi-ast-dart/src/test/resources/grammar/class_example.dart new file mode 100644 index 00000000..1b832cc4 --- /dev/null +++ b/chapi-ast-dart/src/test/resources/grammar/class_example.dart @@ -0,0 +1,89 @@ +library example; + +import 'dart:async'; +import 'dart:collection' show LinkedHashMap; +import 'package:flutter/material.dart' as material; + +/// A simple Person class +class Person { + String name; + int age; + + Person(this.name, this.age); + + Person.fromJson(Map json) + : name = json['name'], + age = json['age']; + + factory Person.anonymous() { + return Person('Anonymous', 0); + } + + String greet() { + return 'Hello, my name is $name'; + } + + @override + String toString() => 'Person($name, $age)'; +} + +/// A student that extends Person +class Student extends Person with Printable implements Comparable { + String school; + + Student(String name, int age, this.school) : super(name, age); + + @override + int compareTo(Student other) { + return name.compareTo(other.name); + } + + void study(String subject) { + print('$name is studying $subject at $school'); + } +} + +/// A mixin for printable objects +mixin Printable { + void printInfo() { + print(toString()); + } +} + +/// An abstract class +abstract class Animal { + String get species; + + void makeSound(); +} + +/// An enum +enum Status { + pending, + active, + completed, + cancelled, +} + +/// An extension on String +extension StringExtension on String { + String capitalize() { + if (isEmpty) return this; + return '${this[0].toUpperCase()}${substring(1)}'; + } +} + +/// A typedef +typedef JsonMap = Map; +typedef Callback = void Function(T value); + +/// Top-level function +void doSomething() { + print('Doing something...'); +} + +/// Top-level getter +String get appVersion => '1.0.0'; + +/// Top-level variable +const String appName = 'DartApp'; diff --git a/chapi-ast-dart/src/test/resources/grammar/hello_world.dart b/chapi-ast-dart/src/test/resources/grammar/hello_world.dart new file mode 100644 index 00000000..2d1413e3 --- /dev/null +++ b/chapi-ast-dart/src/test/resources/grammar/hello_world.dart @@ -0,0 +1,4 @@ +// A simple hello world program +void main() { + print('Hello, World!'); +} diff --git a/settings.gradle.kts b/settings.gradle.kts index eb5b77e4..98c33638 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,7 @@ include( ":chapi-ast-c", ":chapi-ast-cpp", ":chapi-ast-swift", + ":chapi-ast-dart", // others ":chapi-parser-toml",