Skip to content

Latest commit

 

History

History
140 lines (84 loc) · 9.5 KB

CONTRIBUTING.md

File metadata and controls

140 lines (84 loc) · 9.5 KB

Contributing

Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License Agreement. You or your employer retain the copyright to your contribution. This simply gives us permission to use and redistribute your contributions as part of the project.

Pull Requests

Pull requests must pass all CI/CD measures and follow the code specification.

Domain

The domain of Copygen lies in field manipulation. The program uses provided types to determine the fields we must assign. In this context, a Type refers to the types used in a function (as parameters or results) rather than a type used to define variables.

Project Structure

The repository consists of a detailed README, examples, and command line interface.

Command Line Interface

The command-line interface (cli) consists of 5 packages.

Package Description
cli Contains the primary logic used to parse arguments and run the copygen command-line application.
models Contains models based on the application's functionality (business logic).
config Contains external loaders used to configure the file settings and command line options.
parser Uses an Abstract Syntax Tree (AST) and go/types to parse a setup file for fields.
matcher Contains application logic to match fields to each other.
generator Contains the generator logic used to generate code (and interpret templates).

Read program for an overview of the application's code.

Parser

A setup file's abstract syntax tree is traversed once, but involves four processes.

Keep

The setup file is parsed using an Abstract Syntax Tree. This tree contains the type Copygen Interface but also code that must be kept in the generated output file. For example, the package declaration, file imports, convert functions, and custom types all exist outside of the type Copygen Interface. Instead of storing these declarations and attempting to regenerate them, we simply discard declarations — from the setup file's AST — that won't be kept: In this case, the type Copygen Interface and ast.Comments (that refer to Options).

Options

Convert options are defined outside of the type Copygen Interface and may apply to multiple functions. As a result, all ast.Comments must be parsed before models.Function and models.Field objects can be created. In order to do this, the type Copygen Interface is stored, but NOT analyzed until the setup file is traversed.

There are multiple ways to parse ast.Comments into Options, but convert options require the name of their respective convert functions (which can't be parsed from comments). As a result, the most readable, efficient, and least error prone method of parsing ast.Comments into Options is to parse them when discovered; and assign them from a CommentOptionMap later. In addition, regex compilation is expensive — especially in Go — and avoided by only compiling unique comments once.

Copygen Interface

The type Copygen interface is parsed to setup the models.Function and models.Field objects used in the Matcher and Generator.

Imports

The go/types package provides all of the other important information except for alias import names. In order to assign aliased or non-aliased import names to models.Field, the imports of the setup file are mapped to a package path, then assigned to fields prior to matching.

Generator

Copygen supports three methods of generation for end-users (developers): .go, .tmpl, and programmatic.

.go

.go code generation allows users to generate code using the programming language they are familiar with. .go code generation works by allowing the end-user to specify where the .go file containing the code generation algorithm is, then running the file at runtime. In order to do this, we must use an interpreter. Templates are interpreted by our temporary yaegi fork. models objects are extracted via reflection and loaded into the interpreter. Then, the interpreter interprets the provided .go template file (specified by the user) to run the Generate() function.

.tmpl

.tmpl code generation allows users to generate code using text/templates. .tmpl code generation works by allowing the end-user to specify where the .tmpl file containing the code generation algorithm is, then parsing and executing the file at runtime.

programmatic

programmatic code generation allows users to generate code by using copygen as a third-party module. For more information, read the program example README.

Specification

From vs. To

From and To is used to denote the direction of a type or field. A from-field is assigned to a to-field. In contrast, one from-field can match many to-fields. As a result, "From" comes before "To" when parsing while "To" comes before "From" in comparison.

Variable Names

Variable Description
from.* Variables preceded by from indicate from-functionality.
to.* Variables preceded by to indicate to-functionality.

Comments

Comments follow Effective Go and explain why more than what (unless the "what" isn't intuitive).

Why Pointers

Contrary to the README, pointers aren't used — on Fields — as a performance optimization. Using pointers with Fields makes it less likely for a mistake to occur during the comparison of them. For example, using a for-copy loop on a []models.Field:

// A copy of field is created with a separate pointer.
for _, field := range fields {
   // fromField.To still points to the original field.
   // fromField.From points to a field which is NOT the copied field (but has the same values).
   if field == fromField.To {
      // never happens
      ...
   }
}

Anti-patterns

Using the *models.Field definition for a models.Field's Parent field can be considered an anti-pattern. In the program, a models.Type specifically refers to the types in a function signature (i.e func(models.Account, models.User) *domain.Account). While these types are fields (which may contain other fields) , their actual Type properties are not relevant to models.Field. As a result, models.Field objects are pointed directly to maintain simplicity.

Using the *models.Field definition for a models.Field's From and To fields can be placed into a type FieldRelation: From and To is only assigned in the matcher. While either method allows you to reference a models.Field's respective models.Field, directly pointing models.Field objects adds more customizability to the program.

CI/CD

Static Code Analysis

Copygen uses golangci-lint in order to statically analyze code. You can install golangci-lint with go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.1 and run it using golangci-lint run. If you receive a diff error, you must add a diff tool in your PATH. There is one located in the Git bin.

If you receive File is not ... with -..., use golangci-lint run --disable-all --no-config -Egofmt --fix.

Fieldalignment

Struct padding aligns the fields of a struct to addresses in memory. The compiler does this to improve performance and prevent numerous issues on a system's architecture (32-bit, 64-bit). As a result, misaligned fields add more memory-usage to a program, which can effect performance in a numerous amount of ways. For a simple explanation, view Golang Struct Size and Memory Optimization. Fieldalignment can be fixed using the fieldalignment tool which is installed using go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest.

ALWAYS COMMIT BEFORE USING fieldalignment -fix ./cli/... as it may remove comments.

Tests

For information on testing, read Tests.

Roadmap

Focus on these features:

  • Options, Matcher, Generator: cast option for direct type conversions (as opposed to a convert function)
  • Generator: deepcopy
  • Parser: Fix Free-floating comments (add structs in multi to test)