From 91611a5648483a2ece3f3a5ec4bde85440069af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Jakubowski?= Date: Fri, 15 Nov 2024 16:51:43 +0100 Subject: [PATCH] Handle positional constructor parameters properly --- src/data/dart_class.ts | 16 ++- .../file_content/impl/3_0_0_generator.ts | 6 +- src/test/dart_class_parser.test.ts | 111 ++++++++++++++---- src/util/dart_class_parser.ts | 109 ++++++++++++++--- 4 files changed, 197 insertions(+), 45 deletions(-) diff --git a/src/data/dart_class.ts b/src/data/dart_class.ts index 88379c2..6a40ca5 100644 --- a/src/data/dart_class.ts +++ b/src/data/dart_class.ts @@ -9,7 +9,7 @@ class DartClass { class DartClassConstructor { constructor( public named: boolean, - public fields: Array, + public fields: Array, public name: string | null ) {} @@ -26,4 +26,16 @@ class DartClassField { ) {} } -export { DartClass, DartClassConstructor, DartClassField }; +class DartClassConstructorField extends DartClassField { + constructor( + public name: string, + public type: string, + public nullable = false, + public named = true, + ) { + super(name, type, nullable); + } +} + +export { DartClass, DartClassConstructor, DartClassConstructorField, DartClassField }; + diff --git a/src/generators/file_content/impl/3_0_0_generator.ts b/src/generators/file_content/impl/3_0_0_generator.ts index 1077952..da81033 100644 --- a/src/generators/file_content/impl/3_0_0_generator.ts +++ b/src/generators/file_content/impl/3_0_0_generator.ts @@ -49,7 +49,11 @@ class FileContentGenerator3_0_0 extends BaseFileContentGenerator { `; for (const field of constructor.fields) { - output += `${field.name}: ${this.knobForField(field)},\n`; + if (field.named) { + output += `${field.name}: ${this.knobForField(field)},\n`; + } else { + output += `${this.knobForField(field)},\n`; + } } output += ` diff --git a/src/test/dart_class_parser.test.ts b/src/test/dart_class_parser.test.ts index 36ad59d..0cb24e3 100644 --- a/src/test/dart_class_parser.test.ts +++ b/src/test/dart_class_parser.test.ts @@ -1,4 +1,4 @@ -import { DartClassConstructor, DartClassField } from "../data/dart_class"; +import { DartClassConstructor, DartClassConstructorField, DartClassField } from "../data/dart_class"; import { doesLookingFurtherMakeSense, isConstructorLine, @@ -435,11 +435,11 @@ describe("parseLinesToConstructor", () => { " })", ]; const classFields = [ - new DartClassField("active", "bool"), - new DartClassField("semanticsLabel", "String"), + new DartClassConstructorField("active", "bool"), + new DartClassConstructorField("semanticsLabel", "String"), // class can have more fields which are not in the main constructor part because they are in its initializer list - new DartClassField("text", "String"), - new DartClassField("icon", "IconData"), + new DartClassConstructorField("text", "String"), + new DartClassConstructorField("icon", "IconData"), ]; expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( @@ -453,8 +453,8 @@ describe("parseLinesToConstructor", () => { // " const Loader({super.key, this.active = true, this.semanticsLabel});", // ]; // const classFields = [ - // new DartClassField("active", "bool"), - // new DartClassField("semanticsLabel", "String"), + // new DartClassConstructorField("active", "bool"), + // new DartClassConstructorField("semanticsLabel", "String"), // ]; // expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( @@ -473,10 +473,10 @@ describe("parseLinesToConstructor", () => { " })", ]; const classFields = [ - new DartClassField("active", "bool"), - new DartClassField("semanticsLabel", "String"), - new DartClassField("text", "String"), - new DartClassField("icon", "IconData"), + new DartClassConstructorField("active", "bool"), + new DartClassConstructorField("semanticsLabel", "String"), + new DartClassConstructorField("text", "String"), + new DartClassConstructorField("icon", "IconData"), ]; expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( @@ -485,8 +485,39 @@ describe("parseLinesToConstructor", () => { [ classFields[0], classFields[1], - new DartClassField("big", "bool"), - new DartClassField("animated", "bool"), + new DartClassConstructorField("big", "bool"), + new DartClassConstructorField("animated", "bool"), + ], + null + ) + ); + }); + + test("with positional fields and named fields", () => { + const lines = [ + " const Loader(", + " this.active, {", + " super.key,", + " this.semanticsLabel,", + " bool big = false,", + " required bool animated,", + " })", + ]; + const classFields = [ + new DartClassConstructorField("active", "bool"), + new DartClassConstructorField("semanticsLabel", "String"), + new DartClassConstructorField("text", "String"), + new DartClassConstructorField("icon", "IconData"), + ]; + + expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( + new DartClassConstructor( + false, + [ + classFields[0], + classFields[1], + new DartClassConstructorField("big", "bool"), + new DartClassConstructorField("animated", "bool"), ], null ) @@ -502,7 +533,7 @@ describe("parseLinesToConstructor", () => { " required super.active,", " })", ]; - const classFields = [new DartClassField("active", "bool")]; + const classFields = [new DartClassConstructorField("active", "bool")]; expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( new DartClassConstructor(true, [], "small") @@ -518,11 +549,11 @@ describe("parseLinesToConstructor", () => { " }) : big = false,", ]; const classFields = [ - new DartClassField("active", "bool"), - new DartClassField("semanticsLabel", "String"), + new DartClassConstructorField("active", "bool"), + new DartClassConstructorField("semanticsLabel", "String"), // class can have more fields which are not in the main constructor part because they are in its initializer list - new DartClassField("text", "String"), - new DartClassField("icon", "IconData"), + new DartClassConstructorField("text", "String"), + new DartClassConstructorField("icon", "IconData"), ]; expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( @@ -545,10 +576,42 @@ describe("parseLinesToConstructor", () => { " })", ]; const classFields = [ - new DartClassField("active", "bool"), - new DartClassField("semanticsLabel", "String"), - new DartClassField("text", "String"), - new DartClassField("icon", "IconData"), + new DartClassConstructorField("active", "bool"), + new DartClassConstructorField("semanticsLabel", "String"), + new DartClassConstructorField("text", "String"), + new DartClassConstructorField("icon", "IconData"), + ]; + + expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( + new DartClassConstructor( + true, + [ + classFields[0], + classFields[1], + new DartClassConstructorField("big", "bool"), + new DartClassConstructorField("animated", "bool"), + ], + "small" + ) + ); + }); + + + test("with positional fields and named fields", () => { + const lines = [ + " const Loader.small(", + " this.active, {", + " super.key,", + " this.semanticsLabel,", + " bool big = false,", + " required bool animated,", + " })", + ]; + const classFields = [ + new DartClassConstructorField("active", "bool"), + new DartClassConstructorField("semanticsLabel", "String"), + new DartClassConstructorField("text", "String"), + new DartClassConstructorField("icon", "IconData"), ]; expect(parseLinesToConstructor(lines, "Loader", classFields)).toEqual( @@ -557,8 +620,8 @@ describe("parseLinesToConstructor", () => { [ classFields[0], classFields[1], - new DartClassField("big", "bool"), - new DartClassField("animated", "bool"), + new DartClassConstructorField("big", "bool"), + new DartClassConstructorField("animated", "bool"), ], "small" ) diff --git a/src/util/dart_class_parser.ts b/src/util/dart_class_parser.ts index 0ac2e55..79bef32 100644 --- a/src/util/dart_class_parser.ts +++ b/src/util/dart_class_parser.ts @@ -1,6 +1,7 @@ import { DartClass, DartClassConstructor, + DartClassConstructorField, DartClassField, } from "../data/dart_class"; @@ -133,36 +134,40 @@ function parseLinesToConstructor( const constructorName = isNamed ? nameLine.substring(nameLine.indexOf(".") + 1, nameLine.indexOf("(")) : null; + const namedParametersStartIndex = lines.findIndex((line) => + line.includes("{") + ); const fieldsLines = lines .join("") .split(",") // TODO Modify the line below when adding support for one-line constructors .filter((line) => line !== "" && !line.includes("})")); - const classFieldsLines = fieldsLines.filter((line) => - line.includes(classFieldReference) - ); - const classFieldsNames = classFieldsLines.map((line) => { - const lineFromFieldName = line.substringAfter(classFieldReference); - const hasDefaultValue = lineFromFieldName.includes(" = "); - if (hasDefaultValue) { - return lineFromFieldName.substring(0, lineFromFieldName.indexOf(" = ")); - } - return lineFromFieldName; - }); + const positionalClassFieldsLines = fieldsLines.filter( + (line) => + line.includes(classFieldReference) && + fieldsLines.indexOf(line) <= namedParametersStartIndex + ); + const namedClassFieldsLines = fieldsLines.filter( + (line) => + line.includes(classFieldReference) && + fieldsLines.indexOf(line) > namedParametersStartIndex + ); - const classFields = classFieldsNames - .map((fieldName) => - allClassFields.find((field) => field.name === fieldName) - ) - .whereType(); + const classFields = getConstructorClassFields( + positionalClassFieldsLines, + namedClassFieldsLines, + allClassFields + ); const customFieldsLines = fieldsLines.filter( (line) => !line.includes(classFieldReference) && !line.includes("super.") ); const customFields = customFieldsLines.map((line) => { + const named = lines.indexOf(line) > namedParametersStartIndex; + const lineParts = line.trim().split(" "); let name: string; let type: string; @@ -175,10 +180,15 @@ function parseLinesToConstructor( } if (type.endsWith("?")) { - return new DartClassField(name, type.removeTrailing(1), true); + return new DartClassConstructorField( + name, + type.removeTrailing(1), + true, + named + ); } - return new DartClassField(name, type); + return new DartClassConstructorField(name, type, false, named); }); return new DartClassConstructor( @@ -188,6 +198,69 @@ function parseLinesToConstructor( ); } +function getConstructorClassFields( + positionalClassFieldsLines: Array, + namedClassFieldsLines: Array, + allClassFields: Array +): Array { + const classFieldReference = "this."; + + const positionalClassFieldsNames = getClassFieldNames( + positionalClassFieldsLines, + classFieldReference + ); + const namedClassFieldsNames = getClassFieldNames( + namedClassFieldsLines, + classFieldReference + ); + + const positionalClassFields = positionalClassFieldsNames + .map((fieldName) => + allClassFields.find((field) => field.name === fieldName) + ) + .whereType() + .map((field) => { + return classFieldToConstructorField(field, false); + }); + + const namedClassFields = namedClassFieldsNames + .map((fieldName) => + allClassFields.find((field) => field.name === fieldName) + ) + .whereType() + .map((field) => { + return classFieldToConstructorField(field, true); + }); + + return [...namedClassFields, ...positionalClassFields]; +} + +function classFieldToConstructorField( + classField: DartClassField, + named: boolean +): DartClassConstructorField { + return new DartClassConstructorField( + classField.name, + classField.type, + classField.nullable, + named + ); +} + +function getClassFieldNames( + lines: Array, + classFieldReference: string +): Array { + return lines.map((line) => { + const lineFromFieldName = line.substringAfter(classFieldReference); + const hasDefaultValue = lineFromFieldName.includes(" = "); + if (hasDefaultValue) { + return lineFromFieldName.substring(0, lineFromFieldName.indexOf(" = ")); + } + return lineFromFieldName; + }); +} + function doesLookingFurtherMakeSense(line: string): boolean { return ( !line.includes("Widget build(BuildContext context)") &&