Code Monkey home page Code Monkey logo

exhaustivematching's Introduction

ExhaustiveMatching.Analyzer

ExhaustiveMatching.Analyzer adds exhaustive matching to C# switch statements and expressions.

Get compiler errors for missing cases in a switch statement or expression. Mark which switches should have exhaustiveness checking by throwing an exception in the default case. Exhaustiveness checking works not just for enums, but for classes and interfaces. Turn them into discriminated unions (aka sum types) by marking them with the Closed attribute and listing the cases. ExhaustiveMatching.Analyzer goes beyond what other languages support by handling full inheritance hierarchies.

Quickstart Guide

Mark a switch statement or expression as exhaustive and get errors for missing cases.

using ExhaustiveMatching;

public enum CoinFlip { Heads, Tails }

// ERROR Enum value not handled by switch: Tails
switch (coinFlip)
{
    default:
        throw ExhaustiveMatch.Failed(coinFlip);
    case CoinFlip.Heads:
        Console.WriteLine("Heads!");
        break;
}

// ERROR Enum value not handled by switch: Tails
_ = coinFlip switch
{
    CoinFlip.Heads => "Heads!",
    _ => throw ExhaustiveMatch.Failed(coinFlip),
};

Create discriminated unions (aka sum types) and get errors for missing switch cases.

[Closed(typeof(IPv4Address), typeof(IPv6Address))]
public abstract class IPAddress {}

public class IPv4Address : IPAddress {}
public class IPv6Address : IPAddress {}

// ERROR Subtype not handled by switch: IPv6Address
switch (ipAddress)
{
    default:
        throw ExhaustiveMatch.Failed(ipAddress);
    case IPv4Address ipv4Address:
        return ipv4Address.MapToIPv6();
}

Packages and Downloading

All packages are available on NuGet.org. There are three packages available for different situations.

  • ExhaustiveMatching.Analyzer: The standard package that includes all analyzers. Adds a dependency on ExhaustiveMatching.Analyzer to your project.
  • ExhaustiveMatching.Analyzer.Enums (Not Yet Implemented): Only includes the exhaustive matching analyzer for enums using InvalidEnumArgumentException. Avoids adding any dependencies or additional code to your project.
  • ExhaustiveMatching.Analyzer.Source (Not Yet Implemented): For advanced scenarios, avoids adding dependencies to your project while supporting most analyzers by injecting code into your project. See Dependency Free Usage for details.

Versioning and Compatibility

This package does not use semantic versioning. Instead, the first two version numbers match the major and minor version of the Microsoft.CodeAnalysis package referenced by the analyzer. This determines the version of Visual Studio, MSBuild, etc. the analyzer is compatible with. The next two version numbers are the major and minor versions of this package. At any time, multiple versions of Visual Studio may be actively supported. You should use the most recent version compatible with the environment you are using. If an older version is used, functionality will be missing.

Your project must target a framework compatible with .NET Standard 2.0. In addition, a minimum version of Visual studio or .NET Core is required depending on the version of the package you are using. See the table below.

Package Version Minimum Visual Studio Version C# Language
3.8.major.minor Visual Studio 2019 version 16.8, .NET 5 C# 9
3.3.major.minor Visual Studio 2019 version 16.3, .NET Core 3.0 C# 8
2.10.major.minor Visual Studio 2017 version 15.9 C# 7.3
1.3.major.minor Visual Studio 2015 Update 3 C# 6.0
0.x Visual Studio 2019 version 16.3, .NET Core 3.0 C# 8

Usage

Install the ExhaustiveMatching.Analyzer package into each project that will contain exhaustive switch statements, switch expressions, or the classes and interfaces that will be switched on. Additionally, install the package in any project that will reference a project containing types marked with the Closed attribute. This is important because the analyzer enforces rules about inheriting from and implementing closed types. If the analyzer isn't in a project then those rules may be violated without an error being reported. Most of the time, the ExhaustiveMatching.Analyzer can be added to every project in a solution.

As soon as you add the NuGet package to your project, the IDE (Microsoft Visual Studio, JetBrains Rider and possibly others) should automatically enable the analyzer and start marking non-exhaustive switches in your code. Analogously, MSBuild and dotnet CLI should report the errors with no additional set up. If this is not working, you may not be using a compatible version. See Versioning and Compatibility for more information.

Exhaustive Switch on Enum Values

To enable exhaustiveness checking for a switch on an enum, throw an ExhaustiveMatchFailedException from the default case. That exception is constructed using the ExhaustiveMatch.Failed(…) factory method which should be passed the value being switched on. For switches with exhaustiveness checking, the analyzer will report an error for any missing enum cases.

// ERROR Enum value not handled by switch: Sunday
switch(dayOfWeek)
{
    default:
       throw ExhaustiveMatch.Failed(dayOfWeek);
    case DayOfWeek.Monday:
    case DayOfWeek.Tuesday:
    case DayOfWeek.Wednesday:
    case DayOfWeek.Thursday:
    case DayOfWeek.Friday:
        Console.WriteLine("Weekday");
        break;
    case DayOfWeek.Saturday:
        // Omitted Sunday
        Console.WriteLine("Weekend");
        break;
}

Exhaustiveness checking is also applied to switches that throw InvalidEnumArgumentException. This exception indicates that the value doesn't match any of the defined enum values. Thus, if the code throws it from the default case, the developer is expecting that all defined enum cases will be handled by the switch. Using this exception, the throw statement in the above example would be throw new InvalidEnumArgumentException(nameof(dayOfWeek), (int)dayOfWeek, typeof(DayOfWeek));. Since this is longer and less readable, its use is discouraged.

Exhaustive Switch on Type

C# 7.0 added pattern matching including the ability to switch on the type of a value. To ensure any possible value will be handled, all subtypes must be matched by some case. That is what exhaustiveness checking ensures.

To enable exhaustiveness checking for a switch on type, two things must be done. The default case must throw an ExhaustiveMatchFailedException (using the ExhaustiveMatch.Failed(…) factory method) and the type being switched on must be marked with the Closed attribute. The closed attribute makes a type similar to an enum by giving it a defined set of possible cases. However, instead of a fixed set of values like an enum, a closed type has a fixed set of direct subtypes.

CAUTION: subtyping rules for types with the [Closed(...)] attribute are enforced by the analyzer, not the language. They can be circumvented. To prevent this include the exhaustive matching analyzer in all projects in your solution, do not expose a closed type to external code that may not be using exhaustive matching, and do not use dynamic code generation to create subtypes. If unexpected subtypes are created, an ExhaustiveMatchFailedException exception could be generated at runtime.

This example shows how to declare a closed class Shape that can be either a circle or a square.

[Closed(typeof(Circle), typeof(Square))]
public abstract class Shape {}

public class Circle : Shape {}
public class Square : Shape {}

A switch on the type of a shape can then be checked for exhaustiveness.

switch(shape)
{
    case Circle _:
        Console.WriteLine("Circle");
        break;
    case Square _:
        Console.WriteLine("Square");
        break;
    default:
        throw ExhaustiveMatch.Failed(shape);
}

Handling Null

Since C# reference types are always nullable, but may be intended to never be null, exhaustiveness checking does not require a case for null. If a null value is expected it can be handled by a case null:. The analyzer will ignore this case for its analysis.

For nullable enum types, the analyzer requires that there be a case null: to handle the null value.

Type Hierarchies

While a given closed type can only have its direct subtypes as cases, some of those subtypes may themselves be closed types. This allows for flexible switching on multiple levels of a type hierarchy. The exhaustiveness check ensures that every possible value is handled by some case. However, a single case high up in the hierarchy can handle many types.

In the example below, an expression tree is being evaluated. The switch is able to match against multiple levels of the hierarchy while exhaustiveness checking ensures no cases are missing. Notice how the Addition and Subtraction cases are indirect subtypes of Expression, and the Value case handles both Constant and Variable. This kind of sophisticated multi-level switching is not supported in most languages that include exhaustive matching.

[Closed(typeof(BinaryOperator), typeof())]
public abstract class Expression {}

[Closed(typeof(Addition), typeof(Subtraction))]
public abstract class BinaryOperator {}

public class Addition {}
public class Subtraction {}

public abstract class Value {}

public class Constant {}
public class Variable {}

public int Evaluate(Expression expression)
{
    switch(expression)
    {
        case Addition a:
            return Evaluate(a.Left) + Evaluate(a.Right);
        case Subtraction s:
            return Evaluate(s.Left) - Evaluate(s.Right);
        case Value v: // handles both Constant and Variable
            return v.GetValue();
        default:
            throw ExhaustiveMatch.Failed(expression);
    }
}

Analyzer Errors

The analyzer reports various errors for incorrect code. The table below gives a complete list of them along with a description.

Number Description
EM0001 A switch on an enum is missing a case
EM0002 A switch on a nullable enum is missing a null case
EM0003 A switch on type is missing a case
EM0011 A concrete type is not listed as a case in a closed type it is a direct subtype of
EM0012 A case type listed in the closed attribute is not a direct subtype of the closed type (though it is a subtype)
EM0013 A case type listed in the closed attribute is not a subtype of the closed type
EM0014 A concrete subtype of a closed type is not covered by some case
EM0015 An open interface is not listed as a case in a closed type it is a direct subtype of
EM0100 An exhaustive switch can't contain when guards
EM0101 Case pattern is not supported
EM0102 Can't do exhaustiveness checking for switch on a type that is not an enum and not closed
EM0103 Case is for a type that is not in the closed type hierarchy
EM0104 Duplicate 'Closed' attribute on type
EM0105 Duplicate case type

Dependency Free Usage

(Not Yet Implemented)

In some situations, it isn't desirable to add a dependency to your project. For example, a published NuGet package may want to use exhaustive matching without adding a dependency on ExhaustiveMatching.Analyzer to their project. The ExhaustiveMatching.Analyzer.Source package supports these cases. It does so by injecting the source for the ExhaustiveMatch and InternalClosedAttribute directly into the project. This classes are marked internal and will not be visible from outside of the project.

This package does not support the ClosedAttribute because it would not be safe to do so. If a closed class were publicly exposed from your project, then other code could create new subclasses of the closed class thereby making switches on the closed class non-exhaustive.

exhaustivematching's People

Contributors

csharper2010 avatar mristin avatar walkercoderanger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

exhaustivematching's Issues

NullReferenceException in analyzer

System.NullReferenceException: Object reference not set to an instance of an object.
    at ExhaustiveMatching.Analyzer.TypeSymbolExtensions.GetFullName(ISymbol symbol)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.GetTypeSymbolMatched(SyntaxNodeAnalysisContext context, ITypeSymbol type, CasePatternSwitchLabelSyntax casePattern, HashSet`1 allCases, Boolean isClosed)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.<>c__DisplayClass3_0.<AnalyzeSwitchOnClosed>b__1(CasePatternSwitchLabelSyntax casePattern)
    at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
    at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
    at System.Collections.Immutable.DisposableEnumeratorAdapter`2.MoveNext()
    at System.Collections.Immutable.ImmutableHashSet`1.Union(IEnumerable`1 other, MutationInput origin)
    at System.Collections.Immutable.ImmutableHashSet`1.Union(IEnumerable`1 items, Boolean avoidWithComparer)
    at System.Collections.Immutable.ImmutableHashSet`1.Union(IEnumerable`1 other)
    at System.Collections.Immutable.ImmutableHashSet.ToImmutableHashSet[TSource](IEnumerable`1 source, IEqualityComparer`1 equalityComparer)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.AnalyzeSwitchOnClosed(SyntaxNodeAnalysisContext context, SwitchStatementSyntax switchStatement, ITypeSymbol type)
    at ExhaustiveMatching.Analyzer.SwitchStatementAnalyzer.Analyze(SyntaxNodeAnalysisContext context, SwitchStatementSyntax switchStatement)
    at ExhaustiveMatching.Analyzer.ExhaustiveMatchAnalyzer.AnalyzeSwitchStatement(SyntaxNodeAnalysisContext context)

Create a Better Readme

Create a readme that leads with an example. Then goes on to how to install from nuget?

  • Coin flip is a good example of a standard enum
  • IPv6 and IPv4 could be a good object example
  • Need good hierarchy example

Support structurally closed hierarchies like Discriminated Unions

It would be helpful to detect the closed hierarchy of a class with private constructor and nested subclasses having either also a private constructor or a sealed modifier. This is a way to simulate the desparately missing discriminated unions safely in C#.

public abstract class Result<TSuccess, TError> {
    private Result() { }

    public sealed class Success : Result<TSuccess, TError> {
        public TSuccess Value { get; }
        public Success(TSuccess value) { Value = value; }
    }

    public sealed class Error : Result<TSuccess, TError> {
        public TError Value { get; }
        public Error(TError value) { Value = value; }
    }
}

A switch expression like this should provide the error:

var x = result switch
{
    Result<string, string>.Error error => ""Error: "" + error,
    _ => throw ExhaustiveMatch.Failed(result),
};

This would be o.k.:

var x = result ◊1switch{
    Result<string, string>.Error error => ""Error: "" + error,
    Result<string, string>.Success success => ""Success: "" + success,
    _ => throw ExhaustiveMatch.Failed(result),
}

Support logical pattern matching for enums

Using or in enum matches currently displays error EM0001 in the IDE:

sortOrder switch {
    SomeEnum.Unknown or
    SomeEnum.NewestFirst => someValue,
    SomeEnum.OldestFirst => anotherValue,
    => throw ExhaustiveMatch.Failed(sortOrder)
}

Dependency-free builds

I'd love to use this ExhaustiveMatching analyzer with a number of NuGet projects that I work on but I understand that even though this is a "private" dependency, it still causes my project to have a reference to the ExhaustiveMatching.dll assembly which then must be referenced by my output NuGet package - this isn't ideal.

It should be possible for the ExhaustiveMatching code-analysis to work by detecting the use of built-in exception types (e.g. ArgumentOutOfRangeException) in a switch block provided they're annotated with a known magic const string for the message: ctor parameter - or allow use to redefine the ExhaustiveMatching exceptions in our own projects - and the analyzer could then look for that without us needing to reference the ExhaustiveMatching.dll assembly.


On a related note, I'd like to have two separate exceptions to : one derived from ArgumentOutOfRangeException for when switching over an enum parameter argument, and another derived from InvalidOperationException when swiching over an enum that isn't a parameter argument.

Errors Should Be Reported Against Switch Head, Not Body

Currently, a number of diagnostic errors highlight the entire switch statement, including the body. They should only highlight the switch head. Meaning they should highlight just the switch keyword. The switch expression is excluded because it could be long and may have other errors.

Report Error for Multiple Closed Attributes on Declaration

The ClosedAttribute is set AllowMultiple=true because we want to allow partial classes to have a closed attribute on each declaration of the class. However, we don't really want to allow multiple closed attributes on a single class declaration. That is on one of the partial class declarations. Issue an error from the analyzer for multiple closed attributes on a declaration.

Support switch expression

Switch expressions are not being checked. That is a real issue when a suggested refactoring is changing to switch expressions.

Missing part of the readme -- how to actually run this analyzer?

Hi,
I am missing maybe the most obvious part in the readme -- how do I actually run this analyzer? I added it to the project -- is it supposed to automatically run in my IDE now (I use Jetbrains Rider)?

Is there a way to automatically run it in the continuous integration workflows? Just running MSBuild does not produce any errors.

Thanks for your help! (Once I know how to run it, I'll make a PR to document that in the readme since other users might also be lost.)

Add a "Why?" Section to Readme

The readme was planned to have a "Why?" section but it was dropped. Write one. Discussing the expression problem would be good.

Support structurally closed hierarchies like Discriminated Unions based on C# records

Based on the work for #42 it would be nice to have the same feature also für discriminated unions implemented the C# record syntax.

public abstract record Result<TSuccess, TError> {
    private Result() { }

    public sealed record Success(TSuccess Value) : Result<TSuccess, TError>;

    public sealed record Error(TError value) : Result<TSuccess, TError>;
}

A switch with missing arms could also giving the analyzer error however there's a slight source of errors:

It is technically possible to subclass from the Result record outside of the scope because the C# record syntax for non-sealed records always creates a protected copy constructor. Anyway I would argue that the developer intention is clear as there are some intrinsics about what the copy constructor does and where it is used.

This line of code compiles even outside of the Result record but providing a concrete instance (Error in this case) does not make real sense as only the properties of the base record would be considered in the copy constructor:

public record OtherResult(string Value) : Result<string, string>(new Error(Value))

See also this StackOverflow question to see there are also other developers interested in such a feature.

As I understand it however, to really detect that situation, we must switch to Roslyn version 3.9.0 as the IsRecord property was added to the type symbol interface.

Should Support Open Hierarchies

Currently, every subtype of a closed type must be closed, sealed, or struct. Open type hierarchies should be supported, meaning that subtypes don't need to be any of those things. Instead, when matching against the closed type, only its immediate subtype and subtypes of a chain of closed types can be used to match against. That is because exhaustiveness can only be checked for closed types, however it is fine to use an open type to catch all possible subtypes of it.

Note: this adds new error(s) for switch cases trying to match subtypes whose immediate parent aren't closed.

Exceptions in Analyzer

The analyzer still throws exceptions sometimes which show up as warnings in VS. I think it is because of invalid code, but I'm not sure. Things to try:

  • Unknown Type in Case Clause
  • Unknown Type in Case Class List

Transition to Better Term for Member Types

Currently, the subtypes of a closed type are called member types or union of types. There should be a better name for them. Perhaps case types? Change to use the new name consistently.

[Suggestion] Allow optional opt-in for requiring null handling case

Hello!

First of all, I absolutely love this library. I really appreciate your work on it :)

I ran into a case today where I had refactored some code that previously had not allowed null to be a valid value, but now I have switch cases throwing ExhaustiveMatch.Failed. I realized it was because I had not been handling the null case in the switch.

In the documentation, I see you have the following:

Since C# reference types are always nullable, but may be intended to never be null, exhaustiveness checking does not require a case for null. If a null value is expected it can be handled by a case null:. The analyzer will ignore this case for its analysis.

For nullable enum types, the analyzer requires that there be a case null: to handle the null value.

I understand that this might not be desired as a default behavior, but I would really personally like to be forced to handle null on reference types.

Is there any way that we can see this as an opt-in thing in the future? Perhap something we can decorate our assembly with as an attribute, or similar.

Thank you!
Anthony

THANK YOU!

This is an awesome package and I really appreciate your effort.

Analyzer Exception For Invalid Code

It is possible to get exceptions from the analyzer when analyzer invalid code. In particular, I believe that an unrecognized ClosedAttribute can cause it to assume the constructor parameters are not params but individual types.

Add support for exhaustive matching on records

The analyzer does not handle record classes.
for example :

    [Closed(typeof(UserNotFound))]
    public abstract record Response
    {
        public record UserNotFound : Response;

        public record Success : Response;
    }

    class Controller
    {
        public int Get(Response response)
        {
            return response switch
            {
                Response.UserNotFound _ => -1,
                _ => throw ExhaustiveMatch.Failed(response)
            };
        }
    }

The analyzer doesn't detect that the "Success" class is needed to be added to the Closed attribute.
The analyzer doesn't detect that the "Success" clause needs to be handled in the Get() method

Thanks for this library, really appreciate the effort !

Self Type as Case Type Causes Infinite Loop

If a closed type declares itself to be one of its cases, this causes an infinite loop in the analyzer. The analyzer will correctly find an error that the listed type isn't a direct subtype, but other code can't handle this error case.

Strong name the assembly

I had a nasty runtime error just now in a strong-named project targeting .NET Framework 4.8 that uses this NuGet package, as strong-named assemblies transitively require all referenced assemblies to also have strong-names.

I can work-around it with the StrongNamer hack package, but I'd prefer it if this assembly were strong-named properly in the first place.

Microsoft explicitly recommend that libraries distributed as NuGet packages should be strong-named:

You should strong name your open-source .NET libraries. Strong naming an assembly ensures the most people can use it, and strict assembly loading only affects .NET Framework.

Document Mirror Hierarchy

I don't entirely remember what this was supposed to be about. However, it is referencing the unit tests MirrorHierarchy and MirrorHierarchyMustBeCovered, and the example MirrorExample in MirrorHierarchy.cs. I think the point is that using such mirror hierarchies can be confusing and the rules need to be documented. However, at the moment I don't remember the rules I had in mind.

See issue number #26

Allow Mirror Hierarchies

If you create an inheritance hierarchy of interfaces that are closed and a matching hierarchy of classes that implement those interfaces, you'll get errors that the classes aren't case-types of the closed interfaces. However, sometimes this should be allowed. For example:

[Closed(typeof(ISquare), typeof(ICircle))]
interface IShape { }
interface ISquare : IShape { }
interface ICircle : IShape { }

// Gives an error, but is fine as long as all subclasses implement one of the interfaces covered by the case types
abstract class Shape : IShape { }
class Square : Shape, ISquare { }
class Circle : Shape, ICircle { }

Support switch expression and statement matches with literal type cases

The following sitautions are valid switches on a shape.

The type names should be handled correctly by the exhaustive checker.

switch (shape)
{
    case Square square:
        Console.WriteLine(""Square: "" + square);
        break;
    case Circle circle:
        Console.WriteLine(""Circle: "" + circle);
        break;
    case Triangle: // label syntax
        Console.WriteLine(""Triangle!"");
        break;
    default:
        throw ExhaustiveMatch.Failed(shape);
}";

var result = shape switch
{
    Square square => ""Square: "" + square,
    Circle circle => ""Circle: "" + circle,
    Triangle => ""Triangle!"", // type name
    _ => throw ExhaustiveMatch.Failed(shape),
};";

A closed non-abstract class itself shouldn't need to be handled

Hello

In my use case, some of the classes cannot be abstract. They are basic models used for deserialization

example

public enum Kingdom
{
    Animalia,
    Plantae,
}

public enum DogBreed 
{
    Pug,
    Maltese,
}

public enum CatBreed 
{
    Pug,
    Maltese,
}

public class Organism
{
     public Kingdom Kingdom {get; set;}
}

public class Dog : Organism
{
    public DogBreed Breed {get; set; }
}


public class Cat: Organism
{
       public CatBreed Breed {get; set; }
}

public class OrganismHandler
{
    public void Handle(Organism organism)
    {
        switch(organism)
        {
            case Dog dog:
                Handle(dog);
                break;

            case Cat cat:
                Handle(cat)
                break;
            
           // if I do not add a case for class Organism the analyzer complains
           // if I do, it does not complain if I do not add Cat or Dog
           // what I want is to not need to handle organism, would even be ok to get analysis erros if I did
            default: 
                throw ExhaustiveMatch.Failed(regionSpecificForm);
        }

        private void Handle(Dog dog)
        {
                 ....
        }
        
        private void Handle(Cat cat)
        {
                 ....
        }
    }
}

Can't Switch on Case Type in Middle of Heirarchy

If you create a hierarchy of interface case types three deep, you can't use the middle one as a switch case. That is given IRoot, ICase, and IValue each of which implements the previous with IRoot and ICase being closed types, you can't switch on ICase. An error is reported that it "is not a case type inheriting from type being matched".

It appears the issue is that it isn't a concrete type or a leaf type, so it doesn't get put into the list of concrete case types, but it should still be a valid case type. These concepts need to be split.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.