Flutter Dev

Flutter Dev

1638780560

Code generation library and interface for Brick components

Brick Build

Code generator utilities for Brick adapters and model dictionaries.

Setup

It's recommended to use watch when editing models.

(flutter) pub run build_runner watch

If you're not using watch, be sure to run build twice for the schema to detect new migrations on the first run.

(flutter) pub run build_runner build

An application directory will/must resemble the following:

| my-app
|--lib
|--|--app
|--|--|--adapters
|--|--|--models

This ensures a consistent path to access child data, such as models, by build generators.

Table of Contents

  • Glossary
  • API Considerations
    • Provider
      • Class-level Configuration
      • Field-level Configuration
    • Query
      • providerArgs:
      • where:
    • Adapters
    • Models
    • Repository
      • Class-level Configuration
      • Field-level Configuration
      • Associations
  • Code Generation
    • Package Setup
    • Provider
      • Class-level Configuration
      • Field-level Annotation
      • Adapters
      • Invoking the Generators
    • Domain
      • Class-level Annotation
      • Model Dictionary (brick.g.dart)
      • Builder
  • Testing
  • Advanced Techniques
    • Custom Type Checking
  • FAQ

Glossary

  • generator - code producer. The output of a generator is most often a function that converts input to normalized data. The output of a generator does not always constitute a complete file (e.g. one generator is a serializer, another generator is a deserializer, and both generators are combined in a super adapter generator).
  • builder - a class that interfaces between source files and generator(s) before writing generated code to file(s). They are invoked and configured by build.yaml. Builders are primarly concerned with annotations that exist in the source (e.g. a Flutter app).
  • serdes - shorthand for serialize/deserialize
  • checker - an accessible utility that type checks analyzed type from a source. For example, isBool for a source of final bool isDeleted would return true. With a source of final String isDeleted, isBool would return false.
  • domain - the encompassing system. For example, the OfflineFirstWithRest domain builds REST serdes and SQLite serdes within an adapter and is discovered via its own annotation.

API Considerations

Brick is an opinionated library, and providing consistent, predictable interaction regardless of provider or domain is a major goal of the project. Implementing the following guidelines is not a requirement, but please strongly consider them when building custom providers and domains.

Provider

Class-level Configuration

While models should never be aware of providers, a provider's configuration may be used by a repository or supply necessary information to an adapter. As this is accessed via an annotation, configurations must be const. Class-level configuration is useful for setting defaults, describing behavior that relies on an instance:

RestSerializable(
  fieldName: FieldRename.pascal,
  nullable: true,
)

Configuration can also describe behavior that relies on an instance. Since functions cannot be passed in a const class, a stringified function body can be used:

RestSerializable(
  // implied arguments are instance and query
  endpoint: r"=> query.action == QueryAction.get ? '/users' : '/users/${instance.id}'",
)

It is not recommended to require the end implemenation to declare arguments for stringified functions. If the provider's arguments for the property changes, the Dart type system will not detect the error ahead of run time.

:warning: If the provider conflicts with usage of dart:mirrors, the configuration should be hosted in an independent package. For example, brick_sqlite and brick_sqlite_abstract.

Field-level Configuration

A provider may choose to implement configuration at the field-level with annotations. Field-level annotations may be useful to override behavior at a finer level. These annotations should implement FieldSerializable:

@Rest(
  // a property here may override previously-specified behavior at the class-level
  name: "deleted"
)
final bool isDeleted;

@Rest(ignore: true, name: "e-mail")
@Sqlite(unique: true)
final String email;

As the field-level annotations are the most often written, they have the most accessible names. Convention for field-level annotation names is simply the provider name minus "Provider."

:bulb: Keep annotations as atomic as possible. A provider annotation is not reliable to another provider.

Query

Every public instance method should support a named argument of {Query query}. Query is the glue between an application and an abstracted provider or repository. It is accessed by both the repository and the provider, but as the last mile, the provider should interpret the Query at its barest level.

providerArgs:

providerArgs describe how to interact with a provider's source.

providerArgs: {
  // limit describes how many results the provider requires from the source
  'limit': 10,
},

As providerArgs can vary from provider to provider and IDE suggestions are unavailable to a string-key map, providerArgs should be clearly and accessibly documented within every new provider.

where:

where queries with a model's properties. A provider may optionally support where arguments. For example, while a SQLite provider will always support column querying, a RESTful API will likely be less consistent and may require massaging the field name:

[Where('firstName').isExactly('Thomas'), Where('age').isExactly(42)];
// SQLite => SELECT * FROM Users WHERE first_name = "Thomas" AND age = 42;
// REST => https://api.com/users?by_first_name=Thomas&age=42

The translation from model field name (e.g. firstName) to serializer field name (e.g. first_name) may occur in the adapter or in a class-level configuration (e.g. RestSerializable#endpoint). However, it should always be accessed by the provider from the adapter.

Adapters

After the provider receives raw data from its source, it must be built into a model or a list of models. This translation occurs in the adapter. First, the adapter is discovered via the model dictionary, a simple hash table that connects models with adapters

Future<_Model> get<_Model extends RestModel>({Query query}) async {
  // Connects to _ModelAdapter
  final adapter = modelDictionary.forAdapter[_Model];
  final resp = ... // fetch from HTTP

  // Now the provider can (de)serialize
  return response.map((r) => adapter.fromRest(r));
}

The adapter can also facilitate deserialization in the provider with other information about the class:

class UserAdapter {
  // class-level configurations can be copied to the adapter
  final String fromKey = "users";

  // translate field names (provided by Query#where) to their SQLite column names
  final fieldsToSqliteColumns = {
    "primaryKey": {
      "name": "_brick_id",
      "type": int,
      // some information about the type is no longer available after build
      // because this requires mirrors, however, it can be preserved in the adapter
      "iterable": false,
      "association": false,
    },
  };
}

Adapters - made up of both serdes code and custom translation maps such as fieldsToSqliteColumns or restEndpoint - are generated using brick_build.

:warning: A provider should not rely on adapter code generated by another provider library.

Models

When creating a model that the provider relies on, only declare members if they're used by the provider. These members should be considered protected within the provider's ecosystem: their use should be discouraged in the end implementation.

abstract class SqliteModel {
  // the provider relies on the primary key to make associations with other models
  int primaryKey;
}

Repository

Please review best practice methods described in Creating a Custom Repository before designing an interface. Build libraries should not generate repositories, however, sometimes a domain adds extra configuration that requires extending provider generators (for example, OfflineFirstSerdes).

Class-level Configuration

A model annotation should be named Connect<DOMAIN>, include provider configuration, and not manipulate configuration used by other providers. If configuration is required for the repository, it should only be relevant at the repository level:

// BAD
@ConnectOfflineFirstWithRest(
  fieldRename: FieldRename.snake,
  restConfig: RestSerializable(
    // two places to declare the same configuration
    // with no clear logic for the selection hierarchy
    fieldRename: FieldRename.pascal,
  )
)

// GOOD
@ConnectOfflineFirstWithRest(
  restConfig: RestSerializable(
    fieldRename: FieldRename.pascal,
  ),
  sqliteConfig: SqliteSerializable(
    ignore: true,
  ),
  // this property will affect all interactions with the model
  alwaysHydrate: true,
)

Field-level Configuration

Annotations should only reflect configuration relevant to the repository (e.g. directives on how to synthesize). They should not be shortcuts:

// BAD:
@OfflineFirst(ignore: true)

// GOOD:
@Rest(ignore: true)
@Sqlite(ignore: true)

Annotations are most useful when its explicit purpose combines multiple providers and:

@OfflineFirst(where: "{'email': data['email']}")

Unlike atomic provider annotations, repositories can and should access all relevant provider annotations:

// all three of these annotations are useful to the OfflineFirst domain
// when generating adapter code
@Rest(name: 'LastName')
@Sqlite(name: 'last_name')
@OfflineFirst(where: "{'last_name': data['LastName']}")

Associations

Associations can require complex fetching. When a domain supports associations between providers, the class-level annotation should be used in a custom checker. For example, isSibling or isAssociation.

It is recommended to use a repository method dedicated to association fetching instead of the provider, as the repository may route the lookup to a different provider. For example, a User may have 1 Hat, and the repository may already have that Hat in a memory provider. By requesting the repository, the SqliteProvider is spared a potentially expensive query.

Code Generation

Before reading further, this process appears to require a lot of code. This is largely boilerplate required for type checking and Dart's analyzer. The majority of the custom code and logic will live in the adapter serdes.

Package Setup

Annotation and configuration definitions must be declared outside of the build package if they depend on a package that conflicts with mirrors (Flutter conflicts with mirrors). As other packages may use these annotations (for example, OfflineFirst considers @Rest and @Sqlite annotations along with @OfflineFirst), it's safest to keep annotations and builders as independent packages.

For example, the Cloud Firestore package:

brick_cloud_firestore
|--example
|--README.md
|--packages

// this package is the only imported into depedencies: in pubspec.yaml
// it contains the importable provider and repositories
// the repositories may also choose to export annotations from sister packages
|--|--brick_cloud_firestore

// since firestore depends on sqlite, an abstract package includes
// annotations and class-level configuration classes that can be digested by brick_build
// and the brick_cloud_firestore package
|--|--brick_cloud_firestore_abstract

// code generation unique to the provider. exports serialize, deserialize, fields, provider model serdes, and any additional builders
|--|--brick_cloud_firestore_generators

// outputs and saves generated code to disk. this is the only package that includes a build.yaml
// there should only be one discovered annotation per build package
|--|--brick_offline_first_with_cloud_firestore_build
  • [ ] If the provider has a Flutter dependency, a separate package for annotations and configuration exists as a _abstract package
  • [ ] The _generators package does not include a build.yaml (multiple build.yaml files can cause race collisions)
  • [ ] <Provider>Fields, <Provider>SerializeGenerator, <Provider>DeserializeGenerator, and <Provider>ModelSerdesGenerator can be accessed outside the _generators package
  • [ ] Only one class-level annotation is discovered per _build package

Provider

Class-level Configuration

A provider will likely require high-level information about a class that would be inappropriate to define on every instance of a class. For this, a provider can declare a class-level configuration:

RestSerializable(
  // a REST endpoint is inappropriate to define as an instance-level definition
  endpoint: '=> "/users";',
  // class-level configs are also useful for setting a default for subsequent field-level definitions in the class
  fieldRename: FieldRename.snake,
)

These configurations may be injected directly into the adapater (like endpoint) or may change behavior for generated code (like fieldRename).

This class should not be used as an annotation. Instead, it is received as a member of a class-level annotation discovered by the domain.

Interpreting Class-level Configurations

Once a class is discovered by a builder, the configuration is pulled from the annotation and expanded into easily-digestible Dart form:

// RestSerializable is our previously-noted configuration class
class RestModelSerdesGenerator extends ProviderSerializableGenerator<RestSerializable> {
  RestModelSerdesGenerator(Element element, ConstantReader reader)
        // subsequent consumers of this provider generator have to use this config key in their class-level domain annotation
        // or whatever annotation is used to discover the model class
      : super(element, reader, configKey: "restConfig");

  get config {
    if (reader.read(configKey).isNull) return RestSerializable.defaults;

    return RestSerializable(
      // withinConfigKey safely navigates the constantized values, interpreting as digestible Dart code
      endpoint: withinConfigKey("endpoint")?.stringValue ?? RestSerializable.defaults.endpoint,
    );
  }
}

Discovering and Interpreting the Field-level Annotation

Similarly, the field-level annotation must be expanded from their constantized versions back to an easily-digestible form. Brick provides a base class for this:

// @Rest is our annotation AND field-level configuration class, declared via AnnotationFinder<Rest>
class RestAnnotationFinder extends AnnotationFinder<Rest> {
  // this is the previously-defined class-level config
  final RestSerializable config;

  RestAnnotationFinder([this.config]);

  // element is the field, e.g. `final bool isDeleted`
  from(element) {
    // objectForField converts the analyzer's raw data into manageable code
    final obj = objectForField(element);

    // if this field is
    // final bool isDeleted
    // and not
    // @Rest(ignore:)
    // final bool isDeleted
    // then we generate the config with defaults
    if (obj == null) {
      return Rest(
        ignore: Rest.defaults.ignore
      );
    }

    // finally, we reconvert the annotation's configuration to digestible Dart code
    return Rest(
      ignore: obj.getField('ignore').toBoolValue() ?? Rest.defaults.ignore,
    );
  }
}

This reinitializes at the field level. However, a class will require that all fields go through the same process, and so a FieldsForClass class must be made. These fields will be passed to our (de)serialize generators:

// @Rest is still our annotation
// This class is boilerplate and can be safely copied with changes to the type
class RestFields extends FieldsForClass<Rest> {
  final RestAnnotationFinder finder;
  final RestSerializable config;

  RestFields(ClassElement element, [RestSerializable this.config])
      : finder = RestAnnotationFinder(config),
        super(element: element);
}

For providers that do not make use of a class-level config, the Fields implementation can be adjusted:

class RestFields extends FieldsForClass<Rest> {
  final finder = RestAnnotationFinder();

  RestFields(ClassElement element) : super(element: element);
}

Adapters

Adapter serdes generators should be as atomic as possible and not expect code from other adapter generators. By subclassing SerdesGenerator, a _generators package can quickly produce (de)serializing functions for later consumption in a generator:

// FieldSerializable is a protocol for field-level annotations defined in brick_core
abstract class RestSerializeGenerator extends SerdesGenerator<Rest, RestModel> {
  final providerName = 'Rest';
  final doesDeserialize = false;

  RestSerialize(ClassElement element, RestFields fields) : super(element, fields);
}

Every unignored field of a model will pass through the end function of coderForField. This function provides field-level code generation given as much configuration as available:

class RestSerialize extends OfflineFirstGenerator<Rest> {
  // All discovered fields of the class pass through this function for generator output
  // Private fields, methods, static members, and computed setters are automatically ignored
  String coderForField(field, checker, {wrappedInFuture, fieldAnnotation}) {
    // in serialize, the field value will be `instance.fieldName`
    // in deserialize, the field value will be `data['field_name']`
    final fieldValue = serdesValueForField(field, fieldAnnotation.name, checker: checker);
    final defaultValue = SerdesGenerator.defaultValueSuffix(fieldAnnotation);

    if (checker.isString) {
      return fieldValue;
    }

    if (checker.isDateTime) {
      return '$fieldValue?.toIso8601String()';
    }

    // falling through to an unsupported type, null won't add to the generated output
    return null;
  }
}

An adapter always includes serialization and deserialization methods for a provider. At a minimum, all primitive types should be evaluated by the checker and returned to the generator with appropriate serializing or deserializing code. Serdes generators come out as code spaghetti and that's OK. Explicit, verbose declarations - even when duplicated across generators - are reliable and easy to debug.

Adapters can also include useful information such as schema data for a SQLite provider or a function to generate an endpoint for a REST provider. The provider can and should access generic (i.e. not related to a specific model instance) model information via the adapter. Adapter members, like models, should only be declared if they are used by the provider.

// adapter methods and fields are declared in the <PROVIDER>(De)serializeGenerators
List<String> get instanceFieldsAndMethods {
  var toKey = (fields as RestFields).config?.toKey?.trim();

  // Strings should always be wrapped as this is generated code
  // it won't look natural and the type system won't catch errors,
  // so be sure to write comprehensive tests
  if (toKey != null) toKey = "'$toKey'";

  return ['final String toKey = $toKey;'];
}

Lastly, serializing and deserializing generators should live in separate classes for legibility:

class RestSerializeGenerator extends SerdesGenerator<Rest, RestModel> {
  final doesDeserialize = false;
}
class RestDeserialize extends SerdesGenerator<Rest, RestModel> {
  final doesDeserialize = true;
}

Invoking the Generators

The (De)serialize generators are accessed through the <PROVIDER>ModelSerdesGenerator from before:

class RestModelSerdesGenerator extends ProviderSerializableGenerator<RestSerializable> {
  ...

  @override
  get generators {
    final classElement = element as ClassElement;
    // the config expanded previously is now passed to our Fields class
    final fields = RestFields(classElement, config);
    return [
      // the output of these generators will be accessed via a builder in a later step
      RestDeserialize(classElement, fields),
      RestSerialize(classElement, fields),
    ];
  }
}

Domain

The Class-level Annotation

Domain annotations at the class-level are discovered by the domain builder. There must only be one class-level annotation per _build package. The annotation includes configuration options for each provider within the domain:

@ConnectOfflineFirstWithRest(
  // RestSerializable is our configuration body.
  restConfig: RestSerializable(
    endpoint: '=> "/users";',
    fieldRename: FieldRename.snake,
  )
)
class MyModel

Interpreting the Class-level Annotation

As the interpretation of the provider's configuration is handled by the <PROVIDER>ModelSerdesGenerator, forwarding the annotation to the generator is sufficient.

:warning: When declaring the annotation interface, be sure to name the annotation keys after the provider's <PROVIDER>ModelSerdesGenerator#configKey. Otherwise the build package will have to subclass the model generator to lock its config key.

Discovering the Class-level Annotation

An AnnotationSuperGenerator manages sub generators. This generator is the entrypoint for other builders. It should be simple, with most of its logic delegated to sub generators.

// @ConnectOfflineFirstWithRest is the annotation that decorates our domain models
class OfflineFirstGenerator extends AnnotationSuperGenerator<ConnectOfflineFirstWithRest> {
  // required for the adapter output
  final String superAdapterName;

  const OfflineFirstGenerator({
    this.superAdapterName = 'OfflineFirst',
  });

    /// Given an [element] and an [annotation], scaffold generators
  List<SerdesGenerator> buildGenerators(Element element, ConstantReader annotation) {
    // `RestModelSerdesGenerator gathers its configuration from the `ConnectOfflineFirstWithRest` annotation
    final rest = RestModelSerdesGenerator(element, annotation);
    final sqlite = SqliteModelSerdesGenerator(element, annotation);
    return <SerdesGenerator>[]
        ..addAll(rest.generators)
        ..addAll(sqlite.generators);
  }

This class will be used by other generators in the build step.

Model Dictionary (brick.g.dart)

The Model Dictionary generator must generate model dictionaries for each provider. Defining instructions - such as not committing generated code - and guiding code comments - such as the contents of a mapping - are important but not required.

As each model should extend/implement each provider's model type, and each adapter should extend/implement each provider's adapter type, the same dictionary is used for each provider mapping:

// this method is inherited from the super class
final dictionary = dictionaryFromFiles(classNamesToFileNames);

return """
/// REST mappings should only be used when initializing a [RestProvider]
final Map<Type, RestAdapter<RestModel>> restMappings = {
  $dictionary
};
final restModelDictionary = RestModelDictionary(restMappings);

/// Sqlite mappings should only be used when initializing a [SqliteProvider]
final Map<Type, SqliteAdapter<SqliteModel>> sqliteMappings = {
  $dictionary
};
final sqliteModelDictionary = SqliteModelDictionary(sqliteMappings);
""";

To support the maps, every adapter must be included as a part and every model must be included as an import:

// These methods are inherited from the super class
final adapters = adaptersFromFiles(classNamesToFileNames);
final models = modelsFromFiles(classNamesToFileNames);

return """
$models

$adapters
""";

Any imports used within adapters must also be imported:

return """
import 'dart:convert';
import 'package:brick_sqlite/sqlite.dart' show SqliteModel, SqliteAdapter, SqliteModelDictionary;
import 'package:brick_rest/rest.dart' show RestProvider, RestModel, RestAdapter, RestModelDictionary;
// ignore: unused_import, unused_shown_name
import 'package:brick_core/core.dart' show Query, QueryAction;
// ignore: unused_import, unused_shown_name
import 'package:sqflite/sqflite.dart' show DatabaseExecutor;
""";

:bulb: To reduce analyzer errors, include // ignore: unused_import for imports used in part files.

Builder

At long last, the generated code is output to file(s) on disk using a builder.dart file.

The primary build functions will be adapters and the model dictionary, as these are critical to the Brick system:

final offlineFirstGenerator = const OfflineFirstGenerator();

// all models must be aggregated to one file to check associations
Builder offlineFirstAggregateBuilder(options) => AggregateBuilder(requiredImports: [
      "import 'package:brick_offline_first_abstract/annotations.dart';",
      "import 'package:brick_offline_first/offline_first.dart';",
      "import 'package:brick_sqlite_abstract/db.dart';",
    ]);

// The Adapter builder uses the same annotation declared in OfflineFirstGenerator
// it also relies on the `buildGenerators` function
Builder offlineFirstAdaptersBuilder(options) =>
    AdapterBuilder<ConnectOfflineFirstWithRest>(offlineFirstGenerator);

// the model dictionary builder similarly requires the domain's class-level annotation.
// this build will perform optional cleanup as well
Builder offlineFirstModelDictionaryBuilder(options) =>
    ModelDictionaryBuilder<ConnectOfflineFirstWithRest>(
      const OfflineFirstModelDictionaryGenerator(),
      expectedImportRemovals: [
        "import 'package:brick_offline_first_abstract/annotations.dart';",
        'import "package:brick_offline_first_abstract/annotations.dart";',
        "import 'package:brick_offline_first/offline_first.dart';",
        'import "package:brick_offline_first/offline_first.dart";',
      ],
    );

Generators are invoked by builders [in builders.dart] and builders are invoked by build.yaml using Dart's native task runner. As build.yaml can be opaque to the uninitiated and is not part of this repo, documentation about customization can be found on the package page. For basic, battle-tested usage, the build.yaml in this repo can be used as a base and modified appropriately for custom domains.

Testing

Generated code can be compared with expected output using the lib/testing.dart helper utilities.

The source of the code-to-be-generated must be saved in a separate file from the test suite:

// test/generated_source/test_simple.dart
@ConnectMyDomain()
class User extends MyDomainModel {}

// for easy discovery, it's recommended to include the output in the same file
final output = r'''
class MyDomainAdapter....
''';

In the test suite, an expectation can be written:

import 'package:brick_build_test/brick_build_test.dart';
import 'generated_source/test_simple.dart' as _$simple;

final generator = MyDomainGenerator();

test('simple', () {
  final annotation = await annotationForFile<ConnectOfflineFirstWithRest>('generated_source', 'simple');
  final generated = await (generator ?? _generator).generateAdapter(
    annotation?.element,
    annotation?.annotation,
    null,
  );
  expect(generated, _$simple.output);
});

As adapters can often include excess code not related to serialization, such as supporting information for the provider, the scope of the test can be narrowed to only the (de)serialization code:

final generateReader = generateLibraryForFolder('generated_source');
test('simple', () {
  final reader = await generateReader('simple');
  final generated = await generator.generate(reader, null);
  expect(generated, _$simple.output);
});

Advanced Techniques

Custom Type Checking

Most generators may not require an extension of basic type checking (is this a string, is this an int, is this a list). For advanced checking, (e.g. discovering a package-specific class like OfflineFirstSerdes), a new checker will have to be created:

final _serdesClassChecker = TypeChecker.fromRuntime(OfflineFirstSerdes);

class OfflineFirstChecker extends SharedChecker {
  bool get isSerdes => _serdesClassChecker.isAssignableFromType(targetType);
}

For every new or removed type check, always update SharedChecker's computed getter isSerializable.

FAQ

Why are all models hacked into a single file?

Dart's build discovers annotations within one file at a time. Because Brick makes use of associations, it must be aware of all files, including similarly-annotated models that may not be in the same file. Therefore, one build step handles combining all files via a known directory (this is why folder organization is so important) and then combines them into a file. By writing that file, another build step listening for the extension kicks off the next build step to interpret each annotation.

What is GEEK

Buddha Community

Code generation library and interface for Brick components
Tyrique  Littel

Tyrique Littel

1604008800

Static Code Analysis: What It Is? How to Use It?

Static code analysis refers to the technique of approximating the runtime behavior of a program. In other words, it is the process of predicting the output of a program without actually executing it.

Lately, however, the term “Static Code Analysis” is more commonly used to refer to one of the applications of this technique rather than the technique itself — program comprehension — understanding the program and detecting issues in it (anything from syntax errors to type mismatches, performance hogs likely bugs, security loopholes, etc.). This is the usage we’d be referring to throughout this post.

“The refinement of techniques for the prompt discovery of error serves as well as any other as a hallmark of what we mean by science.”

  • J. Robert Oppenheimer

Outline

We cover a lot of ground in this post. The aim is to build an understanding of static code analysis and to equip you with the basic theory, and the right tools so that you can write analyzers on your own.

We start our journey with laying down the essential parts of the pipeline which a compiler follows to understand what a piece of code does. We learn where to tap points in this pipeline to plug in our analyzers and extract meaningful information. In the latter half, we get our feet wet, and write four such static analyzers, completely from scratch, in Python.

Note that although the ideas here are discussed in light of Python, static code analyzers across all programming languages are carved out along similar lines. We chose Python because of the availability of an easy to use ast module, and wide adoption of the language itself.

How does it all work?

Before a computer can finally “understand” and execute a piece of code, it goes through a series of complicated transformations:

static analysis workflow

As you can see in the diagram (go ahead, zoom it!), the static analyzers feed on the output of these stages. To be able to better understand the static analysis techniques, let’s look at each of these steps in some more detail:

Scanning

The first thing that a compiler does when trying to understand a piece of code is to break it down into smaller chunks, also known as tokens. Tokens are akin to what words are in a language.

A token might consist of either a single character, like (, or literals (like integers, strings, e.g., 7Bob, etc.), or reserved keywords of that language (e.g, def in Python). Characters which do not contribute towards the semantics of a program, like trailing whitespace, comments, etc. are often discarded by the scanner.

Python provides the tokenize module in its standard library to let you play around with tokens:

Python

1

import io

2

import tokenize

3

4

code = b"color = input('Enter your favourite color: ')"

5

6

for token in tokenize.tokenize(io.BytesIO(code).readline):

7

    print(token)

Python

1

TokenInfo(type=62 (ENCODING),  string='utf-8')

2

TokenInfo(type=1  (NAME),      string='color')

3

TokenInfo(type=54 (OP),        string='=')

4

TokenInfo(type=1  (NAME),      string='input')

5

TokenInfo(type=54 (OP),        string='(')

6

TokenInfo(type=3  (STRING),    string="'Enter your favourite color: '")

7

TokenInfo(type=54 (OP),        string=')')

8

TokenInfo(type=4  (NEWLINE),   string='')

9

TokenInfo(type=0  (ENDMARKER), string='')

(Note that for the sake of readability, I’ve omitted a few columns from the result above — metadata like starting index, ending index, a copy of the line on which a token occurs, etc.)

#code quality #code review #static analysis #static code analysis #code analysis #static analysis tools #code review tips #static code analyzer #static code analysis tool #static analyzer

Royce  Reinger

Royce Reinger

1658977500

A Ruby Library for Generating Text with Recursive Template Grammars

Calyx

Calyx provides a simple API for generating text with declarative recursive grammars.

Install

Command Line

gem install calyx

Gemfile

gem 'calyx'

Examples

The best way to get started quickly is to install the gem and run the examples locally.

Any Gradient

Requires Roda and Rack to be available.

gem install roda

Demonstrates how to use Calyx to construct SVG graphics. Any Gradient generates a rectangle with a linear gradient of random colours.

Run as a web server and preview the output in a browser (http://localhost:9292):

ruby examples/any_gradient.rb

Or generate SVG files via a command line pipe:

ruby examples/any_gradient > gradient1.xml

Tiny Woodland Bot

Requires the Twitter client gem and API access configured for a specific Twitter handle.

gem install twitter

Demonstrates how to use Calyx to make a minimal Twitter bot that periodically posts unique tweets. See @tiny_woodland on Twitter and the writeup here.

TWITTER_CONSUMER_KEY=XXX-XXX
TWITTER_CONSUMER_SECRET=XXX-XXX
TWITTER_ACCESS_TOKEN=XXX-XXX
TWITTER_CONSUMER_SECRET=XXX-XXX
ruby examples/tiny_woodland_bot.rb

Faker

Faker is a popular library for generating fake names and associated sample data like internet addresses, company names and locations.

This example demonstrates how to use Calyx to reproduce the same functionality using custom lists defined in a YAML configuration file.

ruby examples/faker.rb

Usage

Require the library and inherit from Calyx::Grammar to construct a set of rules to generate a text.

require 'calyx'

class HelloWorld < Calyx::Grammar
  start 'Hello world.'
end

To generate the text itself, initialize the object and call the generate method.

hello = HelloWorld.new
hello.generate
# > "Hello world."

Obviously, this hardcoded sentence isn’t very interesting by itself. Possible variations can be added to the text by adding additional rules which provide a named set of text strings. The rule delimiter syntax ({}) can be used to substitute the generated content of other rules.

class HelloWorld < Calyx::Grammar
  start '{greeting} world.'
  greeting 'Hello', 'Hi', 'Hey', 'Yo'
end

Each time #generate runs, it evaluates the tree and randomly selects variations of rules to construct a resulting string.

hello = HelloWorld.new

hello.generate
# > "Hi world."

hello.generate
# > "Hello world."

hello.generate
# > "Yo world."

By convention, the start rule specifies the default starting point for generating the final text. You can start from any other named rule by passing it explicitly to the generate method.

class HelloWorld < Calyx::Grammar
  hello 'Hello world.'
end

hello = HelloWorld.new
hello.generate(:hello)

Block Constructors

As an alternative to subclassing, you can also construct rules unique to an instance by passing a block when initializing the class:

hello = Calyx::Grammar.new do
  start '{greeting} world.'
  greeting 'Hello', 'Hi', 'Hey', 'Yo'
end

hello.generate

Template Expressions

Basic rule substitution uses single curly brackets as delimiters for template expressions:

fruit = Calyx::Grammar.new do
  start '{colour} {fruit}'
  colour 'red', 'green', 'yellow'
  fruit 'apple', 'pear', 'tomato'
end

6.times { fruit.generate }
# => "yellow pear"
# => "red apple"
# => "green tomato"
# => "red pear"
# => "yellow tomato"
# => "green apple"

Nesting and Substitution

Rules are recursive. They can be arbitrarily nested and connected to generate larger and more complex texts.

class HelloWorld < Calyx::Grammar
  start '{greeting} {world_phrase}.'
  greeting 'Hello', 'Hi', 'Hey', 'Yo'
  world_phrase '{happy_adj} world', '{sad_adj} world', 'world'
  happy_adj 'wonderful', 'amazing', 'bright', 'beautiful'
  sad_adj 'cruel', 'miserable'
end

Nesting and hierarchy can be manipulated to balance consistency with novelty. The exact same word atoms can be combined in a variety of ways to produce strikingly different resulting texts.

module HelloWorld
  class Sentiment < Calyx::Grammar
    start '{happy_phrase}', '{sad_phrase}'
    happy_phrase '{happy_greeting} {happy_adj} world.'
    happy_greeting 'Hello', 'Hi', 'Hey', 'Yo'
    happy_adj 'wonderful', 'amazing', 'bright', 'beautiful'
    sad_phrase '{sad_greeting} {sad_adj} world.'
    sad_greeting 'Goodbye', 'So long', 'Farewell'
    sad_adj 'cruel', 'miserable'
  end

  class Mixed < Calyx::Grammar
    start '{greeting} {adj} world.'
    greeting 'Hello', 'Hi', 'Hey', 'Yo', 'Goodbye', 'So long', 'Farewell'
    adj 'wonderful', 'amazing', 'bright', 'beautiful', 'cruel', 'miserable'
  end
end

Random Sampling

By default, the outcomes of generated rules are selected with Ruby’s built-in pseudorandom number generator (as seen in methods like Kernel.rand and Array.sample). To seed the random number generator, pass in an integer seed value as the first argument to the constructor:

grammar = Calyx::Grammar.new(seed: 12345) do
  # rules...
end

Alternatively, you can pass a preconfigured instance of Ruby’s stdlib Random class:

random = Random.new(12345)

grammar = Calyx::Grammar.new(rng: random) do
  # rules...
end

When a random seed isn’t supplied, Time.new.to_i is used as the default seed, which makes each run of the generator relatively unique.

Weighted Choices

Choices can be weighted so that some rules have a greater probability of expanding than others.

Weights are defined by passing a hash instead of a list of rules where the keys are strings or symbols representing the grammar rules and the values are weights.

Weights can be represented as floats, integers or ranges.

  • Floats must be in the interval 0..1 and the given weights for a production must sum to 1.
  • Ranges must be contiguous and cover the entire interval from 1 to the maximum value of the largest range.
  • Integers (Fixnums) will produce a distribution based on the sum of all given numbers, with each number being a fraction of that sum.

The following definitions produce an equivalent weighting of choices:

Calyx::Grammar.new do
  start 'heads' => 1, 'tails' => 1
end

Calyx::Grammar.new do
  start 'heads' => 0.5, 'tails' => 0.5
end

Calyx::Grammar.new do
  start 'heads' => 1..5, 'tails' => 6..10
end

Calyx::Grammar.new do
  start 'heads' => 50, 'tails' => 50
end

There’s a lot of interesting things you can do with this. For example, you can model the triangular distribution produced by rolling 2d6:

Calyx::Grammar.new do
  start(
    '2' => 1,
    '3' => 2,
    '4' => 3,
    '5' => 4,
    '6' => 5,
    '7' => 6,
    '8' => 5,
    '9' => 4,
    '10' => 3,
    '11' => 2,
    '12' => 1
  )
end

Or reproduce Gary Gygax’s famous generation table from the original Dungeon Master’s Guide (page 171):

Calyx::Grammar.new do
  start(
    :empty => 0.6,
    :monster => 0.1,
    :monster_treasure => 0.15,
    :special => 0.05,
    :trick_trap => 0.05,
    :treasure => 0.05
  )
  empty 'Empty'
  monster 'Monster Only'
  monster_treasure 'Monster and Treasure'
  special 'Special'
  trick_trap 'Trick/Trap.'
  treasure 'Treasure'
end

String Modifiers

Dot-notation is supported in template expressions, allowing you to call any available method on the String object returned from a rule. Formatting methods can be chained arbitrarily and will execute in the same way as they would in native Ruby code.

greeting = Calyx::Grammar.new do
  start '{hello.capitalize} there.', 'Why, {hello} there.'
  hello 'hello', 'hi'
end

4.times { greeting.generate }
# => "Hello there."
# => "Hi there."
# => "Why, hello there."
# => "Why, hi there."

You can also extend the grammar with custom modifiers that provide useful formatting functions.

Filters

Filters accept an input string and return the transformed output:

greeting = Calyx::Grammar.new do
  filter :shoutycaps do |input|
    input.upcase
  end

  start '{hello.shoutycaps} there.', 'Why, {hello.shoutycaps} there.'
  hello 'hello', 'hi'
end

4.times { greeting.generate }
# => "HELLO there."
# => "HI there."
# => "Why, HELLO there."
# => "Why, HI there."

Mappings

The mapping shortcut allows you to specify a map of regex patterns pointing to their resulting substitution strings:

green_bottle = Calyx::Grammar.new do
  mapping :pluralize, /(.+)/ => '\\1s'
  start 'One green {bottle}.', 'Two green {bottle.pluralize}.'
  bottle 'bottle'
end

2.times { green_bottle.generate }
# => "One green bottle."
# => "Two green bottles."

Modifier Mixins

In order to use more intricate rewriting and formatting methods in a modifier chain, you can add methods to a module and embed it in a grammar using the modifier classmethod.

Modifier methods accept a single argument representing the input string from the previous step in the expression chain and must return a string, representing the modified output.

module FullStop
  def full_stop(input)
    input << '.'
  end
end

hello = Calyx::Grammar.new do
  modifier FullStop
  start '{hello.capitalize.full_stop}'
  hello 'hello'
end

hello.generate
# => "Hello."

To share custom modifiers across multiple grammars, you can include the module in Calyx::Modifiers. This will make the methods available to all subsequent instances:

module FullStop
  def full_stop(input)
    input << '.'
  end
end

class Calyx::Modifiers
  include FullStop
end

Monkeypatching String

Alternatively, you can combine methods from existing Gems that monkeypatch String:

require 'indefinite_article'

module FullStop
  def full_stop
    self << '.'
  end
end

class String
  include FullStop
end

noun_articles = Calyx::Grammar.new do
  start '{fruit.with_indefinite_article.capitalize.full_stop}'
  fruit 'apple', 'orange', 'banana', 'pear'
end

4.times { noun_articles.generate }
# => "An apple."
# => "An orange."
# => "A banana."
# => "A pear."

Memoized Rules

Rule expansions can be ‘memoized’ so that multiple references to the same rule return the same value. This is useful for picking a noun from a list and reusing it in multiple places within a text.

The @ sigil is used to mark memoized rules. This evaluates the rule and stores it in memory the first time it’s referenced. All subsequent references to the memoized rule use the same stored value.

# Without memoization
grammar = Calyx::Grammar.new do
  start '{name} <{name.downcase}>'
  name 'Daenerys', 'Tyrion', 'Jon'
end

3.times { grammar.generate }
# => Daenerys <jon>
# => Tyrion <daenerys>
# => Jon <tyrion>

# With memoization
grammar = Calyx::Grammar.new do
  start '{@name} <{@name.downcase}>'
  name 'Daenerys', 'Tyrion', 'Jon'
end

3.times { grammar.generate }
# => Tyrion <tyrion>
# => Daenerys <daenerys>
# => Jon <jon>

Note that the memoization symbol can only be used on the right hand side of a production rule.

Unique Rules

Rule expansions can be marked as ‘unique’, meaning that multiple references to the same rule always return a different value. This is useful for situations where the same result appearing twice would appear awkward and messy.

Unique rules are marked by the $ sigil.

grammar = Calyx::Grammar.new do
  start "{$medal}, {$medal}, {$medal}"
  medal 'Gold', 'Silver', 'Bronze'
end

grammar.generate
# => Silver, Bronze, Gold

Dynamically Constructing Rules

Template expansions can be dynamically constructed at runtime by passing a context map of rules to the #generate method:

class AppGreeting < Calyx::Grammar
  start 'Hi {username}!', 'Welcome back {username}...', 'Hola {username}'
end

context = {
  username: UserModel.username
}

greeting = AppGreeting.new
greeting.generate(context)

External File Formats

In addition to defining grammars in pure Ruby, you can load them from external JSON and YAML files:

hello = Calyx::Grammar.load('hello.yml')
hello.generate

The format requires a flat map with keys representing the left-hand side named symbols and the values representing the right hand side substitution rules.

In JSON:

{
  "start": "{greeting} world.",
  "greeting": ["Hello", "Hi", "Hey", "Yo"]
}

In YAML:

---
start: "{greeting} world."
greeting:
  - Hello
  - Hi
  - Hey
  - Yo

Accessing the Raw Generated Tree

Calling #evaluate on the grammar instance will give you access to the raw generated tree structure before it gets flattened into a string.

The tree is encoded as an array of nested arrays, with the leading symbols labeling the choices and rules selected, and the trailing terminal leaves encoding string values.

This may not make a lot of sense unless you’re familiar with the concept of s-expressions. It’s a fairly speculative feature at this stage, but it leads to some interesting possibilities.

grammar = Calyx::Grammar.new do
  start 'Riddle me ree.'
end

grammar.evaluate
# => [:start, [:choice, [:concat, [[:atom, "Riddle me ree."]]]]]

Roadmap

Rough plan for stabilising the API and features for a 1.0 release.

VersionFeatures planned
0.6block constructor
0.7support for template context map passed to generate
0.8method missing metaclass API
0.9return grammar tree from #evaluate, with flattened string from #generate being separate
0.10inject custom string functions for parameterised rules, transforms and mappings
0.11support YAML format (and JSON?)
0.12API documentation
0.13Support for unique rules
0.14Support for Ruby 2.4
0.15Options config and ‘strict mode’ error handling
0.16Improve representation of weighted probability selection
0.17Return result object from #generate calls

Credits

Author & Maintainer

Contributors

Author: Maetl
Source Code: https://github.com/maetl/calyx 
License: MIT license

#ruby #text 

Samanta  Moore

Samanta Moore

1621137960

Guidelines for Java Code Reviews

Get a jump-start on your next code review session with this list.

Having another pair of eyes scan your code is always useful and helps you spot mistakes before you break production. You need not be an expert to review someone’s code. Some experience with the programming language and a review checklist should help you get started. We’ve put together a list of things you should keep in mind when you’re reviewing Java code. Read on!

1. Follow Java Code Conventions

2. Replace Imperative Code With Lambdas and Streams

3. Beware of the NullPointerException

4. Directly Assigning References From Client Code to a Field

5. Handle Exceptions With Care

#java #code quality #java tutorial #code analysis #code reviews #code review tips #code analysis tools #java tutorial for beginners #java code review

Houston  Sipes

Houston Sipes

1604088000

How to Find the Stinky Parts of Your Code (Part II)

There are more code smells. Let’s keep changing the aromas. We see several symptoms and situations that make us doubt the quality of our development. Let’s look at some possible solutions.

Most of these smells are just hints of something that might be wrong. They are not rigid rules.

This is part II. Part I can be found here.

Code Smell 06 - Too Clever Programmer

The code is difficult to read, there are tricky with names without semantics. Sometimes using language’s accidental complexity.

_Image Source: NeONBRAND on _Unsplash

Problems

  • Readability
  • Maintainability
  • Code Quality
  • Premature Optimization

Solutions

  1. Refactor the code
  2. Use better names

Examples

  • Optimized loops

Exceptions

  • Optimized code for low-level operations.

Sample Code

Wrong

function primeFactors(n){
	  var f = [],  i = 0, d = 2;  

	  for (i = 0; n >= 2; ) {
	     if(n % d == 0){
	       f[i++]=(d); 
	       n /= d;
	    }
	    else{
	      d++;
	    }     
	  }
	  return f;
	}

Right

function primeFactors(numberToFactor){
	  var factors = [], 
	      divisor = 2,
	      remainder = numberToFactor;

	  while(remainder>=2){
	    if(remainder % divisor === 0){
	       factors.push(divisor); 
	       remainder = remainder/ divisor;
	    }
	    else{
	      divisor++;
	    }     
	  }
	  return factors;
	}

Detection

Automatic detection is possible in some languages. Watch some warnings related to complexity, bad names, post increment variables, etc.

#pixel-face #code-smells #clean-code #stinky-code-parts #refactor-legacy-code #refactoring #stinky-code #common-code-smells

I am Developer

1597817005

Bar Code Generator In Laravel 7.x

Bar Code Generate in Laravel 7, 6. In this post, i will show you simple and easy steps to generate bar/qr code in laravel.

Generate Bar codes in Laravel

Use the below given steps and generate bAR/QR codes in laravel Projects:

  1. Install Laravel Fresh Setup
  2. Set database Credentials In .env File
  3. Install simple-QRcode Package
  4. Register Package
  5. Test Qr Code
  6. Conclusion

https://www.tutsmake.com/laravel-6-simple-generate-or-create-qr-codes-example/

#laravel 7 bar code generator #barcode generator laravel 7 #barcode generator laravel 6 #laravel 7 qr code generator #laravel simple/barcode example