obi1kenobi / cargo-semver-checks Goto Github PK
View Code? Open in Web Editor NEWScan your Rust crate for semver violations.
License: Apache License 2.0
Scan your Rust crate for semver violations.
License: Apache License 2.0
Since the rustdoc JSON data format is unstable, new nightly Rust releases will change the output and make this tool no longer work until it is adapted for the new output format.
Set up a GitHub Action to periodically re-run the test suite with the latest Rust nightly so we can discover these problems as early as possible.
Currently, before cargo publish
, the new version of cargo-semver-checks
is tested by running the most-recent-prior version of cargo-semver-checks
on its API changes. This makes sure we don't break semver for any public APIs, in preparation for #67.
It would be nice to test the new release with itself as well, for two reasons:
For me, its up to the reviewer to decide if a compatibility break is acceptable. I would love to be able to label the PR.
This can either come from direct support or documentation on how to integrate with other actions
Current, everything is under cargo semver-checks check-release
. Deeply nested subcommands like that should be in rare cases and we should be cautious of having long command names like that.
Do we need that extra subcommand? Is there another way of expressing that idea?
Let's first breakdown expected workfows (which will help with #47).
The overall aim is to help with API evolution,. including
There has long been an RFC for "private" dependencies, which would help crates ascertain that they don't depend on a (particular version of) a crate in public API. Unfortunately it seems like doing this is complex enough in Cargo that no one has gotten around to it.
From reading your blog post it seems like this tool would have access to all the relevant data to allow it to warn when a dependency’s type or trait appears in public API.
This is an important part of semver since bumping a public dependency across a semver-incompatible boundary of course means your own semver is affected, too.
It would be great if there was a way to configure this tool to allow or deny dependencies from appearing in public API.
No response
No response
With --rev
, --tag
, --branch
flags so someone can compare the API against a repo
This would be exclusive with #50
Hi! I have some concerns about use of rustdoc
for non-documentation purposes. In my experience rustdoc
regularly has regressions/bugs (a recent example w/ my crate: rust-lang/rust#100204; a long standing bug: rust-lang/rust#83375) (I'm sorry, rustdoc
team ;-;) which make it a questionable choice for correctness checks like this crate.
A better (in my opinion anyway) way of getting information about crates would be to directly call compiler APIs. The drawback though is that compiler APIs are unstable, so then this tool would need to be shipped with the compiler like clippy
or rustdoc
that are linked with a specific version of the compiler (I'm not sure how doable this is). And of course, there is an implementation cost, someone needs to do this.
See clap-cargo for help
This is a list of all not-yet-implemented checks that would be useful to have. Some of these require new schema and adapter implementations as well, tracked in #241.
In addition to checking for semver violations, there are certain changes that are not breaking and don't even require a minor version, but can still be frustrating in downstream crates without a minor or major version bump. Crates should be able to opt into such warnings on an individual basis.
For example, based on this poll (with small sample size: ~40 respondents), ~40% of users expect that upgrading to a new patch version of a crate should not generate new lints or compiler warnings. The split between expecting a new minor version and a new major version was approximately 3-to-1.
#[non_exhaustive]
: #143repr(C)
plain struct has fields reordered#[derive(PartialOrd)]
pub fn
moved, deleted, or renamed (#22, #23, #24)pub fn
changed return type: blocked on #149pub fn
added argumentpub fn
removed argumentpub fn
changed arguments in a backward-incompatible way
&str
to taking S: Into<String>
without breaking#[no_mangle]
or #[export_name]
functions: #502repr(C)
removed from struct or enum (#25)repr(transparent)
removed from struct or enum (#26, #28)repr(u*)
and repr(i*)
changed/removed from enum (#29, #30)repr(align(N))
changed to a lower N
, if that actually changes the alignment of the type
N
doesn't immediately cause a breaking change, because one of the contained fields has higher alignment requirements and ends up in practice causing the overall type's alignment to remain unchanged.repr(align(N))
changed to a higher N
, if that actually changes the alignment of the type
repr(packed(N))
changed to a lower N
repr(align(N))
with lower N
discussed above -- we probably want a lint for thisrepr(packed)
is also valid syntax! the N
is implied as N=1
if missingrepr(packed(N))
changed to a higher N
repr(align(N))
with higher N
discussed above -- we should probably hold off until we can get layout info for fieldsSized / Send / Sync / Unpin / UnwindSafe / RefUnwindSafe
(auto traits) (#31)Copy
(appears to be breaking because of rust-lang/rust#100905 )prelude
module that gets imported with a wildcard
&T, Box
etc.)
?Sized
on a trait associated type, breaks fn bad<T: Tr>() -> T::Assoc
: https://twitter.com/withoutboats/status/1701274760653533637?Sized
on a trait associated type, breaks impls that used an unsized type there?Sized
bound from from a generic type in a function or type signature?Sized
bound to a generic type in a function or type signature: #532impl Trait
?Sized
in return position impl Trait
(say -> Box<impl Foo>
changing to -> Box<impl Foo + ?Sized>
)impl Trait
in return type.
pub fn changed return type
pub type
typedef changes the order of generic arguments (regular, lifetime, or const generics) relative to the underlying typepub type
typedef adds a new generic parameterpub type
typedef removes a generic parameterpub type
typedef removes a default value for a generic parameterpub type
typedef changes a default value for a generic parameterDrop
implementations changing type parameter lifetime requirements
pub fn blah(extern "C" fn())
to pub fn blah(extern "C-unwind" fn())
will almost certainly break peoplepub const
moved, deleted, or renamed
pub static
is still breaking: cannot use static
in const fn
; cannot initialize another const with a static like pub const MY_CONST = other::PREV_CONST_NOW_STATIC
pub static
moved, deleted, or renamed -- but not changed to pub const
impl
blockpub static
changed to pub const
, with caveats
impl
blocklet foo: &'static T = &MY_UNSAFE_CELL_STATIC
is fine but doesn't work if the value is a const
instead of static
std::mem::needs_drop::<T>()
(not the same as T: Drop
-- String
is not drop itself but its contents need dropping)
const
but works fine for static
serde::Serialize + serde::Deserialize
gains new fields that are not #[serde(default)]
pub type
typedef adds a default value for a generic parameterFor example, because they are technically breaking but projects may often treat them as non-major.
cargo-breaking
README file#[non_exhaustive]
type from another crate to remain a 1-ZST usable in #[repr(transparent)]
pub type
is not equivalent to pub use
-- enum (or pub use
) replaced by pub type
is currently breaking because use upstream::Enum::*
won't work through pub type
Debug
i.e. the Rust missing_debug_implementations
lint: https://twitter.com/Lucretiel/status/1558287048892637184new() -> Self
method should be Default
: https://rust-lang.github.io/api-guidelines/interoperability.htmlFusedIterator
in generic bounds, instead use Iterator.fuse()
: https://doc.rust-lang.org/std/iter/trait.FusedIterator.html#[inline]
on a pub fn
in the public API, since that may have unexpected negative perf impact in some cases (e.g. slower cargo test
if that crate is a dependency and compiled with opt-level = 3
): https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Cargo.20wizard.3A.20automating.20Cargo.20project.20configuration/near/425816132serde::Serialize + serde::Deserialize
has private fields
#[non_exhaustive]
from an item
#[non_exhaustive]
can be done in a patch release, but adding it back would then require a new major version.#[non_exhaustive]
enum
pub
field in an exhaustive public struct
#[non_exhaustive]
and have only public fields can be constructed with a struct literal. Removing the ability to construct a struct with a struct literal is a breaking change and requires a new major version.pub mod
and is also exported with pub use
, it can become importable in multiple ways. This is easy to miss. Removing an import path is breaking, so perhaps we should warn that this is happening. Related to #35.#[enforce_1zst]
attribute could signal that the type should remain a 1-ZST and that deviations from that are breaking.#[non_exhaustive]
, until rust-lang/rust#78586 is resolved and prevents this.Send/Sync/Sized/Unpin
or other auto traits, when it previously wasn't.
Cargo is dual-licensed so I assume dual licensing will make it easier to merge. The sooner, the better to get buy-in from all copyright holders (ie contributors)
Note:: I am speaking from a position of ignorance on licensing
Right now, glob imports are ignored for purposes of calculating the "importable paths" of an item.
This is likely to cause false-negative issues where items aren't scanned but they should be. If moving from a non-glob to a glob pub use
, this could also cause a false-positive "item was removed" issue. The circumstances to trigger a false-positive are exceedingly rare, but this should be fixed soon regardless.
There are valid use cases for running additional (not-necessarily-semver) user-provided checks against a codebase. A few examples:
&mut self
or self
and doesn't mix across them.These are all reasonable queries runnable over the existing schema and Trustfall adapter used here. We just need to design the CLI for specifying the additional checks, and the file format (serde struct) the additional check files should use.
Tangentially related to #5.
I was trying cargo-semver-check for the first time on a small project of mine and this was the result:
$ cargo semver-checks check-release
Updating index
Parsing escape8259 v0.5.1 (current)
Parsing escape8259 0.5.0 (baseline)
Error: Failed to parse rustdoc JSON output file "/home/eric/work/rust/escape8259/target/semver-checks/registry-escape8259-0_5_0/target/semver-checks/target/doc/escape8259.json"
Caused by:
missing field `name` at line 1 column 774
My project is here; my working directory is clean and I have the top of the master branch checked out (09529d6).
My default toolchain is 1.63, and I cargo-semver-check is 0.9.0.
On Windows 10:
git clone https://github.com/Bromeon/js-sandbox.git
cd js-sandbox
cargo semver-checks check-release -p js-sandbox
cargo semver-checks check-release -p js-sandbox
Updating index
Parsing js-sandbox v0.2.0-rc.0 (current)
Error: Failed when running cargo-doc on <path>\js-sandbox\Cargo.toml: error: no such subcommand: `+nightly`
Getting output about semver compatibility, or at least a logic-related error.
I tried with both cargo-semver-checks versions 0.9.1 and 0.9.2, same results.
I also tried alternative ways to invoke Cargo:
rustup run nightly cargo semver-checks check-release
Updating index
Parsing js-sandbox v0.2.0-rc.0 (current)
Error: Failed when running cargo-doc on <path>\js-sandbox\Cargo.toml: error: no such subcommand: `+nightly`
Cargo does not handle `+toolchain` directives.
Did you mean to invoke `cargo` through `rustup` instead?
It works when I run it on WSL2 (Linux) from the same Windows machine.
cargo-semver-checks 0.9.2
Windows 6.2.9200
C:\Users\<me>\.cargo\bin\cargo-semver-checks.exe semver-checks check-release -p js-sandbox --bugreport
> cargo +nightly -V
cargo 1.65.0-nightly (6da726708 2022-08-23)
It would be useful to export the discovered semver issues and/or warnings in a machine-readable format (e.g. JSON).
For example, this would allow the automatic inclusion of changes that may cause lints in downstream crates into a changelog. See this tweet for details: https://twitter.com/russellrcohen/status/1549065325186007044
Related to #5.
Not sure what the easiest and best way to do this is.
Every so often, new Rust nightlies might cause the rustdoc JSON output format to change in a backward-incompatible way.
cargo-semver-checks
will remain compatible with the most recent nightly's output, and we have a test that ensures that. However, users may still be using an older (or newer!) nightly that is incompatible with their cargo-semver-checks
version.
If the JSON fails to parse, we should:
cargo-semver-checks
version and we should advise the user to try upgrading to a newer cargo-semver-checks
(as in #98)git clone https://github.com/diesel-rs/diesel
cd diesel
git checkout v2.0.0
cargo semver-checks check-release --manifest-path diesel/Cargo.toml --baseline-version 1.4.8
cargo semver-checks check-release --manifest-path diesel/Cargo.toml --baseline-version 1.4.8
Updating index
Parsing diesel v2.0.0 (current)
Parsing diesel 1.4.8 (baseline)
Error: Failed to parse rustdoc JSON output file "/home/weiznich/Documents/rust/diesel/target/semver-checks/target/doc/diesel.json"
Caused by:
recursion limit exceeded at line 1 column 203398
(The column number varies between different runs)
cargo semver-checks check-release
reports a number of API breaking changes between diesel 1.4 and diesel 2.0
No response
cargo-semver-checks 0.9.1
Linux 5.18.18-200.fc36.x86_64
/home/weiznich/.cargo/bin/cargo-semver-checks semver-checks --bugreport
> cargo +nightly -V
cargo 1.65.0-nightly (4ed54cecc 2022-08-27)
Sometimes it's useful to be able to change the alert level of specific lints for a particular item or an entire module. Clippy and rustc lints have already stabilized syntax like #[deny(lint_name)]
and #[allow(lint_name)]
, and our syntax choices would ideally be reasonably compatible with those.
The kinds of controls we'd like to include are:
The lint level control adjustments should allow both raising and lowering the level of a given lint. For example:
Semver says deprecations are minor changes.
Reference: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute
Will require separate checks for:
GlobalValue
)Unfortunately, rustdoc
JSON output does not seem to output re-export information in paths
, so we don't seem to be able to get information on re-exports which we can query for semver issues.
Upstream tracking issue: rust-lang/rust#93522
Blockers
Nice-to-haves
Open questions
cargo
or separate rustup component like clippy?Things to remove
_
is hidden (blocked on rust-lang/cargo#10882)bench
, nostd
, no_std
, private
, unstable
, unstable[-_].*
, nightly
(blocked on rust-lang/cargo#10882, rust-lang/cargo#10881)--default-features
, --only-explicit-features
Technically, semver says that these are minor changes.
Not all want to treat it as such and is an example of why people would want control over lints treatments, see #58
Currently, termcolor
detection thinks that GitHub Actions don't support color, but they do. Setting CARGO_TERM_COLOR=always
makes cargo's own output colorized but cargo-semver-checks
output remains plain, which is jarring.
https://doc.rust-lang.org/cargo/reference/config.html#termcolor
https://docs.rs/termcolor/latest/termcolor/enum.ColorChoice.html
The API extends past the rust code to the manifest
Possible checks include
dep:
reference to optional dependency (informative)Consider a more cargo test
/ cargo nextest
-like output, where the individual tests show as pass/fail but the summary is at the very end. This would speed up the time-to-known-outcome, as the result iterators would only be .peek()
-ed once and then saved until the final summary is generated. This is the laziest of all possible evaluations.
Also consider fewer sections in the left-hand side, and maybe focus on just a "Help" section together with each failure in the final summary. This way, folks don't have to figure out what each section does.
If a crate has one or more previously published versions and you have been making changes to master
without incrementing the version in Cargo.toml
,¹ running cargo semver-checks check-release
does not behave as it should; namely, it assumes the version number in Cargo.toml
is unpublished and when searching for prior versions of the crate does not use the available information to correct that assumption.
The behavior differs depending on the crate's publication history:
Cargo.toml
still has that same version number, cargo-semver-checks
(as of 0.9.2) reports "No published versions for ..."cargo-semver-checks
will check the current Cargo.toml
version (assuming it to be represented by the current code in git) against any previous releases rather than checking the current code against the version indicated in Cargo.toml
.I won't presume to tell you that you can't assume Cargo.toml
is updated to a new version number as soon as the previous version ships, but I can tell you that this assumption isn't going to cover everyone.
¹ a common school of thought holds that your git tag x.y.z
should be the commit that bumps the official version number to x.y.z
See above
If the version in Cargo.toml
is discovered to already have been published, ideally cargo-semver-checks
would continue as if the patch version were being incremented and report changes diffed against the already published version with the same version number currently in Cargo.toml
.
Failing that (and that would be really disappointing if we can't make this happen - I'm willing to help), then an error should be emitted as soon as it is discovered that the version number in Cargo.toml
has already been published. This would prevent the currently unambiguously incorrect behavior (ranging from incorrect error messages to diffing against the wrong version) from being reached, at the very least.
I was going to open a PR with just the following change, but I realized that it wasn't enough because that would only cover the case where a crate has only one previously published version and Cargo.toml
still points to that version number:
diff --git a/src/baseline.rs b/src/baseline.rs
index 06f0d20..1b9714f 100644
--- a/src/baseline.rs
+++ b/src/baseline.rs
@@ -222,7 +222,7 @@ impl BaselineLoader for RegistryBaseline {
.rev()
.find(|v| v.pre.is_empty())
.or_else(|| instances.last())
- .with_context(|| anyhow::format_err!("No published versions for {}", name))?;
+ .with_context(|| anyhow::format_err!("No published versions for {}. Did you try incrementing the version in Cargo.toml?", name))?;
instance.to_string()
} else {
let instance = crate_
N/A
I'm thinking "if this were merged into cargo, what name should it have?"
Quick thoughts on criteria
Ideas
cargo api
(currently taken)cargo crate-api
(I have this one)cargo diff
(available)cargo preflight
(reserved by @obi1kenobi)Like #6 being an output mode, this would be another that would provide a snippet for including in a changelog. No-touch additions (automatically insert into file, using a particular users format, etc) are non-goals. The focus is on a starting point as the user will likely want to add more information as well and we want to encourage users to do so rather than it being "good enough".
The hope is this will encourage people to write changelogs more.
For unknown reasons, rustdoc JSON output sometimes does not include information about all the traits implemented by a type unless --document-private-items
is specified. For example, in this clap
semver regression:
UnwindSafe
is implemented for ArgMatches
: cargo rustdoc --lib --all-features -- --document-private-items -Zunstable-options --output-format json
UnwindSafe
for ArgMatches
at all: cargo rustdoc --lib --all-features -- -Zunstable-options --output-format json
It's unclear to me at the moment whether this is expected behavior from rustdoc, or a bug. Will triage and use this as a tracking issue.
In the meantime:
--document-private-items
and --all-features
in the rustdoc invocationThe language needs to convey that this is incomplete as
It is possible to use #[doc(hidden)]
to implement seemingly-breaking changes without actually breaking semver compatibility.
Since cargo-semver-checks
only uses the json rustdoc output to form its analysis, the claim that cargo-semver-checks
has no false positives should probably have this shortcoming noted in the readme.
e.g. these two versions of the same code below are technically semver compatible:
pub struct Foo {}
pub struct Bar {}
#[doc(hidden)]
pub struct Foo {}
pub struct Bar {}
as merely hiding a type (or as is more often, a method) from the documentation doesn't break the semver contract.
or, for a more interesting case, the following:
pub struct Fo {}
pub struct Foo {}
// The previous release included a typo in the type name so the following is kept for backwards-compatibility:
#[doc(hidden)]
pub type Fo = Foo;
There is a lot more dark magic that can done that would break docs but preserve backwards compatibility. Relying solely on the docs is a great and efficient way of implementing straightforward semver breakage cases, but I don't think it's fair to claim that any tool using this approach has no false positives.
A breaking change is reported.
No breaking change should be reported (though I realize this is fundamentally not possible for this crate as it is written).
As such, the "no false positives" claim should be removed or at least the caveats thoroughly mentioned.
No response
No response
With just cargo semver-checks
in package directory, it should take the current version and select the previous one. For pre-release versions, it should select the previous non-pre-release as breaking changes between prereleases is fine.
At the moment, rustdoc is coupled to a specific range of nightly versions. We should clearly document this for users (and maybe provide helpful runtime errors about it) so users who let their nightly versions go stale have a positive path forward.
Needs Trustfall support for tagging a value inside a @fold
and then using it outside the fold.
The query for "the import specifically is missing, but the item is present" would look something like:
{
CrateDiff {
baseline {
item {
... on Enum {
visibility_limit @filter(op: "=", value: ["$public"]) @output
name @output @tag
# this is the part that isn't supported at the moment:
# we currently can't use this tag outside of its own @fold scope
importable_path @fold {
path @tag
}
importable_path {
missing_path: path @tag
}
span_: span @optional {
filename @output
begin_line @output
}
}
}
}
current {
item {
... on Enum {
visibility_limit @filter(op: "=", value: ["$public"])
name @filter(op: "=", value: ["%name"])
# at least one of the importable paths matches, so we know this should be the same enum
importable_path @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) {
path @filter(op: "one_of", value: ["%path"]) # tagged value is list-typed because of fold
}
# but some importable path is also missing from the current paths, which is the error
importable_path @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
path @filter(op: "=", value: ["%missing_path"])
}
}
}
}
}
}
The query for "the item itself is gone, it's not just imports" would look like this:
{
CrateDiff {
baseline {
item {
... on Enum {
visibility_limit @filter(op: "=", value: ["$public"]) @output
name @output @tag
importable_path @fold {
path @tag
}
span_: span @optional {
filename @output
begin_line @output
}
}
}
}
current @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
item {
... on Enum {
visibility_limit @filter(op: "=", value: ["$public"])
name @filter(op: "=", value: ["%name"])
# at least one of the importable paths matches, so we know this should be the same enum
importable_path @fold @transform(op: "count") @filter(op: ">", value: ["$zero"]) {
path @filter(op: "one_of", value: ["%path"]) # tagged value is list-typed because of fold
}
}
}
}
}
}
https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#explicit-repr-annotation-with-c-compatibility
Currently, I suspect that #[repr(C, u8)]
won't get detected as either #[repr(C)]
or as #[repr(u8)]
, which I believe can lead to both false-positives and false-negatives. Due to the false-positives, this is a bug.
H/t https://twitter.com/bitshiftmask/status/1562185690208735233 + a private Twitter account I'm not going to name, you know who you are :) Thank you both!
This blog post describes how to make unnameable types i.e. pub types that can be returned from the public API but can't be imported and used directly because they reside in a private module:
https://seanmonstar.com/post/693574545047683072/pattern-matching-and-backwards-compatibility
The post's has this example:
#[non_exhaustive]
pub enum Error {
Connect(Connect),
// other top-level errors
}
#[non_exhaustive]
pub enum Connect {
Resolve(unnameable::Resolve),
Handshake(unnameable::Handshake),
}
mod unnameable {
pub struct Resolve(());
pub struct Handshake(());
}
It seems that some of the semver rules might not apply in the usual ways to these types. For example, maintainers might frequently decide that renaming an unnameable type shouldn't be considered breaking.
We should define cargo-semver-checks' behavior with respect to unnameable types and back it up with test cases.
Providing a library alongside the cli tool would be much appreciated for tools like https://GitHub.com/paritytech/cargo-unleash even before it becomes part of cargo
Related: #61
semverver is another tool for checking semver violations. Its approach at a high level is to compare rlibs rather than rustdocs.
semverver was originally published as part of GSoC 2017, but has recently been adopted into the rust-lang org to hopefully keep it functional.
Obviously there will be differences in what changes both tools do and don't catch, and keeping a scorecard of that probably isn't really worth the effort. It would be nice, however, to call out semverver as an alternative and list some high-level differences resulting from the different approaches. As an initial intuition, that would include at least
#[doc(hidden)]
items, which semverver has access to to check.Personally, I think diffing the documented surface is the "more correct" approach here, as semver is about public exposed API. Relying on details not contained in the documentation is relying on details outside of the semver guarantee.
Prebuilt binaries can cut ~2-3min from the execution time of the cargo-semver-checks
GitHub Action. Other Rust binary crates tend to make and upload prebuilt binaries as part of the release process, and make them part of the GitHub release. We should probably follow suit.
Pre-build binaries for the most common targets and upload them to each GitHub release.
Action that might be useful: https://github.com/taiki-e/upload-rust-binary-action#example-workflow-cross-compilation
Limit the GitHub token permissions like the following: https://github.com/taiki-e/cargo-hack/blob/202e6e59d491c9202ce148c9ef423853267226db/.github/workflows/release.yml#L3-L5
Available target runners on GitHub Actions: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners
Examples from other crates:
No response
No response
Ran this on clap and finally tried to do some work in the repo again just to find that git status
is reporting all sorts of crazy stuff.
With the implementation, I was hoping to do something light weight like a shallow clone but apparently I have no idea how that git2 call works
Consider the following code, in an imaginary lib
crate.
pub mod foo {
pub struct Bar;
}
pub use foo::Bar;
Bar
is now importable both directly as lib::Bar
and as lib::foo::Bar
. If one of these import paths becomes unavailable in a future release, that's a breaking change.
I believe (but haven't verified) that this should get caught by the current checks, but the error message will be misleading: it will say that the item was removed or renamed, and will not provide the import path that stopped being available.
There is currently a test case that non-breaking moves do not cause false-positive errors. We'll also need a test that breaking moves and import changes cause true-positive errors with a good error message.
Compare the currently selected package against one from the registry at --base-version
Like the compiler, help for the lints should be built-in.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.