Code Monkey home page Code Monkey logo

wrapcheck's Introduction

Wrapcheck

Go Report Card Tests

A simple Go linter to check that errors from external packages are wrapped during return to help identify the error source during debugging.

More detail in this article

Install

Go >= v1.16

$ go install github.com/tomarrell/wrapcheck/v2/cmd/wrapcheck@v2

Wrapcheck is also available as part of the golangci-lint meta linter. Docs and usage instructions are available here. When used with golangci-lint, configuration is integrated with the .golangci.yaml file.

Configuration

You can configure wrapcheck by using a .wrapcheck.yaml file in either the local directory, or in your home directory.

# An array of strings which specify substrings of signatures to ignore. If this
# set, it will override the default set of ignored signatures. You can find the
# default set at the top of ./wrapcheck/wrapcheck.go.
ignoreSigs:
- .Errorf(
- errors.New(
- errors.Unwrap(
- errors.Join(
- .Wrap(
- .Wrapf(
- .WithMessage(
- .WithMessagef(
- .WithStack(

# An array of strings which specify regular expressions of signatures to ignore.
# This is similar to the ignoreSigs configuration above, but gives slightly more
# flexibility.
ignoreSigRegexps:
- \.New.*Error\(

# An array of glob patterns which, if any match the package of the function
# returning the error, will skip wrapcheck analysis for this error. This is
# useful for broadly ignoring packages and/or subpackages from wrapcheck
# analysis. There are no defaults for this value.
ignorePackageGlobs:
- encoding/*
- github.com/pkg/*

# ignoreInterfaceRegexps defines a list of regular expressions which, if matched
# to a underlying interface name, will ignore unwrapped errors returned from a
# function whose call is defined on the given interface.
ignoreInterfaceRegexps:
- ^(?i)c(?-i)ach(ing|e)

Usage

To lint all the packages in a program:

$ wrapcheck ./...

Testing

This linter is tested using analysistest, you can view all the test cases under the testdata directory.

TLDR

If you've ever been debugging your Go program, and you've seen an error like this pop up in your logs.

time="2020-08-04T11:36:27+02:00" level=error error="sql: error no rows"

Then you know exactly how painful it can be to hunt down the cause when you have many methods which looks just like the following:

func (db *DB) getUserByID(userID string) (User, error) {
	sql := `SELECT * FROM user WHERE id = $1;`

	var u User
	if err := db.conn.Get(&u, sql, userID); err != nil {
		return User{}, err // wrapcheck error: error returned from external package is unwrapped
	}

	return u, nil
}

func (db *DB) getItemByID(itemID string) (Item, error) {
	sql := `SELECT * FROM item WHERE id = $1;`

	var i Item
	if err := db.conn.Get(&i, sql, itemID); err != nil {
		return Item{}, err // wrapcheck error: error returned from external package is unwrapped
	}

	return i, nil
}

The problem here is that multiple method calls into the sql package can return the same error. Therefore, it helps to establish a trace point at the point where error handing across package boundaries occurs.

To resolve this, simply wrap the error returned by the db.Conn.Get() call.

func (db *DB) getUserByID(userID string) (User, error) {
	sql := `SELECT * FROM user WHERE id = $1;`

	var u User
	if err := db.Conn.Get(&u, sql, userID); err != nil {
		return User{}, fmt.Errorf("failed to get user by ID: %v", err) // No error!
	}

	return u, nil
}

func (db *DB) getItemByID(itemID string) (Item, error) {
	sql := `SELECT * FROM item WHERE id = $1;`

	var i Item
	if err := db.Conn.Get(&i, sql, itemID); err != nil {
		return Item{}, fmt.Errorf("failed to get item by ID: %v", err) // No error!
	}

	return i, nil
}

Now, your logs will be more descriptive, and allow you to easily locate the source of your errors.

time="2020-08-04T11:36:27+02:00" level=error error="failed to get user by ID: sql: error no rows"

A further step would be to enforce adding stack traces to your errors instead using errors.WithStack() however, enforcing this is out of scope for this linter for now.

Why?

Errors in Go are simple values. They contain no more information about than the minimum to satisfy the interface:

type Error interface {
  Error() string
}

This is a fantastic feature, but can also be a limitation. Specifically when you are attempting to identify the source of an error in your program.

As of Go 1.13, error wrapping using fmt.Errorf(...) is the recommend way to compose errors in Go in order to add additional information.

Errors generated by your own code are usually predictable. However, when you have a few frequently used libraries (think sqlx for example), you may run into the dilemma of identifying exactly where in your program these errors are caused.

In other words, you want a call stack.

This is especially apparent if you are a diligent Gopher and always hand your errors back up the call stack, logging at the top level.

So how can we solve this?

Solution

Wrapping errors at the call site.

When we call into external libraries which may return an error, we can wrap the error to add additional information about the call site.

e.g.

...

func (db *DB) createUser(name, email, city string) error {
  sql := `INSERT INTO customer (name, email, city) VALUES ($1, $2, $3);`

  if _, err := tx.Exec(sql, name, email, city); err != nil {
    // %v verb preferred to prevent error becoming part of external API
    return fmt.Errorf("failed to insert user: %v", err)
  }

  return nil
}

...

This solution allows you to add context which will be handed to the caller, making identifying the source easier during debugging.

Contributing

As with most static analysis tools, this linter will likely miss some obscure cases. If you come across a case which you think should be covered and isn't, please file an issue including a minimum reproducible example of the case.

License

This project is licensed under the MIT license. See the LICENSE file for more details.

wrapcheck's People

Contributors

butuzov avatar clamyang avatar dependabot[bot] avatar frecks avatar guillaumeio avatar jacalvo avatar jpalardy avatar r2k1 avatar timkral avatar tomarrell avatar y-tajiri 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  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  avatar

wrapcheck's Issues

Linter triggers on wrapper functions

Considering following code:

// Deploy checks current status of deployed group of instances and updates them if there is some
// configuration drift.
func (a *apiLoadBalancers) Deploy() error {
  return a.containers.Deploy()
}

Right now linter triggers on it, but it seems very counter-intuitive.

Used via golangci-lint version v1.39.0

Ability to ignore specific error types

I'm creating a CLI app, and I'm using a package named promptui to display fancy prompts. If a user presses Ctrl+D or Ctrl+C while a prompt is active, this package returns specific error types to indicate this. These error types are statically declared, and I don't have any need to wrap these errors before passing them on.

It would be nice if there was a way to exclude specific errors, rather than any error from a given package.

Support for ignoring regular match files

For example, the methods in a bootstrap.go are all used to start the program, and I just want to process them uniformly at the location where the service starts, instead of wrapping each function

image

Currently, I need to handle something like the following, too many nolint:wrapcheck flags, not very friendly

image

Was v1.0.0 republished?

It looks like the v1.0.0 tag may have been published multiple times with different commits, leading to module checksum errors:

go: downloading honnef.co/go/tools v0.1.3
verifying github.com/tomarrell/[email protected]: checksum mismatch
	downloaded: h1:Vlt2WgQOtsuhOBvJsqnT79c0BmN568PxEcB+EMNm/yY=
	go.sum:   h1:e/6yv/rH08TZFvkYpaAMrgGbaQHVFdzaPPv4a5EIu+o=

SECURITY ERROR
This download does NOT match an earlier download recorded in go.sum.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.

pkg.go.dev indicates that v1.0.0 was published on 3/17/2021, but this repo indicates it was published yesterday, 3/29/2021.

False-positive for anonymous functions

Wrapcheck throws an error for the next case:

import "errors"

func Test_AnonFunc(t *testing.T) {
	test := func() error {
		return errors.New("test")
	}
	fmt.Println(test())
}

error returned from external package is unwrapped: sig: func errors.New(text string) error

Great linter! Just one question about %w vs %v

Hi,

Forgive my question but I think there is a reason you are using %v rather than %w on errorf to wrap errors but I am not sure I understand it.

I have always been using %w with returning to new errors that wrap previous ones but I notice here you are using %v.

What is the reason ?

Thanks again!

false positive messages on legal errors from the same file

file internal/modifier/modifiers_aggregate.go contains code:

var (
    ErrPlaylistsNotTwo = errors.New("playlists not two")
)

if len(playlists) != 2 {
    return nil, ErrPlaylistsNotTwo
}

causes the following:
internal/modifier/modifiers_aggregate.go:197:15: error returned from external package is unwrapped (wrapcheck)
return nil, ErrPlaylistsNotTwo

Proposal: ignore returning value of specify package's function

Hello ๐Ÿ‘‹
Thank you for developing such a great tool!

I'm using a library that wraps and manages errors like below.

https://github.com/morikuni/failure
https://github.com/pkg/errors

Currently, wrapcheck also reports for these librarie's function.

example

error returned from external package is unwrapped (wrapcheck)
                return nil, errors.Wrap(err, "failed to do something")
                                  ^

This is, of course, the correct behavior. But I don't need these report from wrapcheck.
So, I ignore these reports with golangci-lint's exclude-rules like below.

issues:
  exclude-rules:
   - source: "errors"
      linters:
        - wrapcheck
    - source: "failure"
      linters:
        - wrapcheck

https://golangci-lint.run/usage/configuration/

It would be very helpful to be able to configure these settings on the wrapcheck side.

(This proposal may be similar to #2)

Thanks

Integrate wrapcheck to golangci-lint ?

Hi, thank you for the useful lint tool. Have you ever considered to integrate it into golangci-lint, which may be the most widely used lint tool in golang ?

False positive when return errors.New()

I have a function that returns a new error:

func (s SomeType) Validate() error {
	if s.Id == 0 {
		return errors.New("validation: id is required")
	}

        return nil
}

When I run golangci-lint with wrapckeck enabled I have an error: error returned from external package is unwrapped: sig: func errors.New(text string) error (wrapcheck)

consider flagging errors.Wrap(err) where err is known to be nil

I found several cases in my codebase (kopia/kopia#747) that follow the following buggy pattern:

err := doSomething()
if err != nil {
  return result, errors.Wrap(err, "something failed")
}

if somethingElse {
  return result, errors.Wrap(err, "something else failed")
}

The second errors.Wrap() is incorrect, because it's not wrapping any error (but it's easy to copy/paste it as such).

This is quite hard to spot in code reviews because lines that return errors will always have errors.Wrap() and it looks ok at first glance, until you notice that in this case is err is always nil. Because errors.Wrap(nil, "something") always returns nil this one returns success, which is unintended.

Based on flow analysis, it should be possible to determine that err == nil in this case, and it would be really amazing if the linter could flag this pattern is as misuse.

Embedded interfaces are hard to ignore

When an interface is embedded, the reported interface name isn't what you need to ignore. Concretely, extend wrapcheck/testdata/config_ignoreInterfaceRegexps/main.go with the following, and run the test.

type embedder interface {
	errorer
}

func embed(fn embedder) error {
	var str string
	return fn.Decode(&str) // errorer interface ignored as per `ignoreInterfaceRegexps`
}

Unexpectedly, you'll see the following:

--- FAIL: TestAnalyzer (5.40s)
--- FAIL: TestAnalyzer/config_ignoreInterfaceRegexps (0.25s)
analysistest.go:452: /home/murman/src/wrapcheck/wrapcheck/testdata/config_ignoreInterfaceRegexps/main.go:33:9: unexpected diagnostic: error returned from interface method should be wrapped: sig: func (_/home/murman/src/wrapcheck/wrapcheck/testdata/config_ignoreInterfaceRegexps.errorer).Decode(v interface{}) error

Note that the ignores include errorer and the reported interface is config_ignoreInterfaceRegexps.errorer. However to suppress this you actually need to suppress embedder.

I believe this comes down to the difference between name and fnSig in reportUnwrapped, which come from sel.Sel and sel.X respectively, but the path forward is unclear to me.

name := types.TypeString(pass.TypesInfo.TypeOf(sel.X), func(p *types.Package) string { return p.Name() })
if containsMatch(regexpsInter, name) {
} else {
pass.Reportf(tokenPos, "error returned from interface method should be wrapped: sig: %s", fnSig)

Incorrect alert on io.Reader implementation

If you have code which implements io.Reader that delegates the call, then currently wrapcheck throws a warning.

func (c *sshConnection) Read(p []byte) (n int, err error) {
	return c.channel.Read(p)
}

This caused us to implement wrapping, but unfortunately this causes issues with code such as bufio.Scanner.Err() as the error is now not io.EOF but a wrapped io.EOF causing it to incorrectly return an error instead of nil.

I originally raised this as bug in bufio.Scanner however that was rejected as io.Reader is documented as returning io.EOF and not a wrapped io.EOF.

Given this I believe delegated methods which form part of io and in particular io.Reader should not be reported as it will lead to similar issues.

Support for errors.Wrap(f)

Hello,

In our project we use golangci-lint and have a lot of similar code:

func foo() (object, error)
  err := doSomething()
  if err != nil {
    return nil, errors.Wrapf(err, "action failed for %v", anyAdditionalInfo)
  }
}

Starting from version golangci-lint 1.39.0 built from 9aea4aee on 2021-03-26T08:02:53Z all those lines cannot pass wrapcheck:

foo/bar:42:16: error returned from external package is unwrapped (wrapcheck)
                         return nil, errors.Wrapf(err, "action failed for %v", anyAdditionalInfo)

Is that a false positive error ? Or am I missing something ?

Thanks.

ignorePackageGlobs doesn't appear to be working

Hey, great tool! This has been really useful to track down places I can apply some new error context packages I've been working on to provide additional structured information to errors.

After applying error contexts across our project I noticed a few places where it was unnecessary so I decided to disable reporting of imports from local packages (/pkg, etc)

However, maybe I'm misunderstanding this config option, but it doesn't appear to do what I expected.

Here's the configuration file: https://github.com/Southclaws/storyden/blob/main/.golangci.yml#L28

And here are the offending lines:

I assumed that a ignorePackageGlobs value of github.com/Southclaws/storyden/* would ignore these two lines as they are functions that are imported from within this pattern:

  • i.thread_svc.Create: github.com/Southclaws/storyden/pkg/services/thread.Service.Create
  • i.thread_svc.ListAll: github.com/Southclaws/storyden/pkg/services/thread.Service.Create

The actual output from running golangci-lint run:

storyden on ๎‚  main [!] via ๐Ÿน v1.18.3 on โ˜๏ธ  (eu-central-1) 
โฏ golangci-lint run

pkg/transports/http/bindings/threads.go:39:10: error returned from interface method should be wrapped: sig: func (github.com/Southclaws/storyden/pkg/services/thread.Service).Create(ctx context.Context, title string, body string, authorID github.com/Southclaws/storyden/pkg/resources/account.AccountID, categoryID github.com/Southclaws/storyden/pkg/resources/category.CategoryID, tags []string) (*github.com/Southclaws/storyden/pkg/resources/thread.Thread, error) (wrapcheck)
                return err
                       ^
pkg/transports/http/bindings/threads.go:52:10: error returned from interface method should be wrapped: sig: func (github.com/Southclaws/storyden/pkg/services/thread.Service).ListAll(ctx context.Context, before time.Time, max int) ([]*github.com/Southclaws/storyden/pkg/resources/thread.Thread, error) (wrapcheck)
                return err
                       ^

I wondered if it was golangci lint just using an outdated version but this occurs on the latest standalone version of wrapcheck too.

I tried with a .wrapcheck.yaml file with:

ignorePackageGlobs:
  - github.com/Southclaws/storyden/*
  - github.com/Southclaws/storyden/pkg/*
  - pkg/*
  - ./pkg/*

but I can't seem to get any patterns to ignore correctly.

Thanks!

Clarifiaction on internal custom errors

First, thank you so much for this great linter. It's already brought me so much sanity, like a lost desert wanderer coming across an oasis ๐Ÿ๏ธ!

It seems that this doesn't, and indeed shouldn't, generate a lint message?

package main

import (
	"os"
	"github.com/pkg/errors"
)

var CustomError = errors.New("๐Ÿ‘ท")

func main() {
	do()
}

func do() error {
	_, err := os.Open("/doesnt/exist")
	if err != nil {
		return CustomError
	}
	return nil
}

But my intuition from the proposition of this project is that it should. And it seems like that opinion is, or at least was, shared by others, judging by discussions in #3 and #6?

What's the current official position and reasoning? And are there any recommendations for helping ensure that return CustomError is wrapped?

Named return values not reported

Try this out

package main

import (
	"errors"
)

func somethingDangerous() error {
	return errors.New("fake err")
}

func run() (err error) {
	err = somethingDangerous()
	return
}

func main() {
	if err := run(); err != nil {
		println(err)
	}
}

You'll see that wrap check does not detect that err is not being wrapped.

Why should I wrap variable errors created with errors.New?

import (
	"github.com/pkg/errors"
)

var (
	errInvalidDuration = errors.New("invalid duration")
)
config/config.go:152:10: error returned from external package is unwrapped (wrapcheck)
                return errInvalidDuration

Why should I wrap this error? It is a variable defined in the same package and it already has stack trace.

Ability to ignore calls wrapped in a helper function

The package lo defines a series of helper functions named TryOr:

https://github.com/samber/lo#tryor

These functions accept a function and a default value. If the function returns an error, the default value is returned instead.

In these cases, I don't care what the error is, and it's discarded inside the helper function. I don't have a need to wrap these errors, but right now I don't have a good way to ignore those cases.

wrapcheck panic after upgrading to go 1.22

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6cafde]

goroutine 104 [running]:
go/types.(*Checker).handleBailout(0xc0003a0600, 0xc0009f9c60)
    /usr/local/go/src/go/types/check.go:367 +0x88
panic({0x81e080?, 0xba8470?})
    /usr/local/go/src/runtime/panic.go:770 +0x132
go/types.(*StdSizes).Sizeof(0x0, {0x942860, 0xbad080})
    /usr/local/go/src/go/types/sizes.go:228 +0x31e
go/types.(*Config).sizeof(...)
    /usr/local/go/src/go/types/sizes.go:333
go/types.representableConst.func1({0x942860?, 0xbad080?})
    /usr/local/go/src/go/types/const.go:76 +0x9e
go/types.representableConst({0x944040, 0xb86640}, 0xc0003a0600, 0xbad080, 0xc0009f93d0)
    /usr/local/go/src/go/types/const.go:92 +0x192
go/types.(*Checker).representation(0xc0003a0600, 0xc00031e340, 0xbad080)
    /usr/local/go/src/go/types/const.go:256 +0x65
go/types.(*Checker).implicitTypeAndValue(0xc0003a0600, 0xc00031e340, {0x942888, 0xc0001982a0})
    /usr/local/go/src/go/types/expr.go:375 +0x30d
go/types.(*Checker).assignment(0xc0003a0600, 0xc00031e340, {0x942888, 0xc0001982a0}, {0x893ce4, 0x14})
    /usr/local/go/src/go/types/assignments.go:52 +0x2e5
go/types.(*Checker).initConst(0xc0003a0600, 0xc001c181e0, 0xc00031e340)
    /usr/local/go/src/go/types/assignments.go:126 +0x336
go/types.(*Checker).constDecl(0xc0003a0600, 0xc001c181e0, {0x943180, 0xc0002300c0}, {0x943180, 0xc0002300e0}, 0x0)
    /usr/local/go/src/go/types/decl.go:490 +0x348
go/types.(*Checker).objDecl(0xc0003a0600, {0x947ef8, 0xc001c181e0}, 0x0)
    /usr/local/go/src/go/types/decl.go:191 +0xa49
go/types.(*Checker).packageObjects(0xc0003a0600)
    /usr/local/go/src/go/types/resolver.go:693 +0x4dd
go/types.(*Checker).checkFiles(0xc0003a0600, {0xc00022e020, 0x2, 0x2})
    /usr/local/go/src/go/types/check.go:408 +0x1a5
go/types.(*Checker).Files(...)
    /usr/local/go/src/go/types/check.go:372
golang.org/x/tools/go/packages.(*loader).loadPackage(0xc0001421c0, 0xc0007a1d20)
    /root/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:1001 +0x76f
golang.org/x/tools/go/packages.(*loader).loadRecursive.func1()
    /root/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:838 +0x1a9
sync.(*Once).doSlow(0x0?, 0x0?)
    /usr/local/go/src/sync/once.go:74 +0xc2
sync.(*Once).Do(...)
    /usr/local/go/src/sync/once.go:65
golang.org/x/tools/go/packages.(*loader).loadRecursive(0x0?, 0x0?)
    /root/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:826 +0x4a
golang.org/x/tools/go/packages.(*loader).refine.func2(0x0?)
    /root/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:761 +0x26
created by golang.org/x/tools/go/packages.(*loader).refine in goroutine 1
    /root/go/pkg/mod/golang.org/x/[email protected]/go/packages/packages.go:760 +0xc9a

Upgrading dependency golang.org/x/tools to v0.18.0 fix the issue.

wrapcheck on goerr113 wrapping %w

Wrapcheck on goerr113 wrapping %w

I understand that Wrapcheck linter is all about wrapping the error coming from external packages but I think the error coming from the external package should never be used as %w reason being that this forces DevUsers to import the package A where the error is being returned as well as the internal package B where the error is being thrown and possible where the Sentinel lives if we ever need to check on the error type with Error.Is and Error.As creating a form of coupling to the internal detail implementation of package A

import "pkg/Process"

var ErrProccesFailed = "process failed due"
{...}
err := Process()
return fmt.Errorf("%w : %v", ErrProccesFailed,err)

I think Sentinel errors need to be created to wrap the err coming from the external package so only one import is needed to check on the Error.Is or Error.As meanwhile, we make sure the external package error is wrapped into a more meaningful error at this package level without exposing any type of possible coupling

Flag to ignore main module

As has been requested, ignoring the main module in a project via a flag to prevent having to configure the package ignore for each project.

Something along the lines of ignore: go list -m.

HTTP Handlers Errors

Most of the go http frameworks has the error as return in their handlers, so we can add them into the ignore pattern if you agree.

New release?

I'd like to use the new ignorePackageGlobs feature

d409df3

But it wasn't released yet.

Do you plan to release it?

false positive messages with using of github.com/pkg/errors

This code causes a false-positive error:

import "github.com/pkg/errors"


func FooFunc() err {
 b, err := BarFunc()
 if err != nil {
  return errors.Wrap(err, "this line causes: error returned from external package is unwrapped")
 }
}

func BarFunc() (bool, err) {
 // ...
}

Version: golangci-lint has version 1.39.0 built from 9aea4aee on 2021-03-26T08:02:53Z

Configuration to define what is external

Thanks for the nice linter!

I was curious if you considered cases where the meaning of external could mean something other than outside the current package. In my case I would like to find cases where errors from outside the current module or -local string are not wrapped. I see the levels being package, module, organization. If this interests you I'd like to work on it with you otherwise I could simply fork the project

Support Gorm-style error checking

Gorm uses non-standard error format to support it's chainable API. It would be nice if there was a way to configure wrapcheck to match a *.\.Error regex

tx := tx.Find(&customer)
if tx.Error != nil {
    return nil, tx.Error // wrapcheck should warn here
}


abc := tx.Find(&customer)
if abc.Error != nil {
    return nil, abc.Error // wrapcheck should warn here
}

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.