Giter VIP home page Giter VIP logo

recast's Introduction

recast, v. CI Join the chat at https://gitter.im/benjamn/recast

  1. to give (a metal object) a different form by melting it down and reshaping it.
  2. to form, fashion, or arrange again.
  3. to remodel or reconstruct (a literary work, document, sentence, etc.).
  4. to supply (a theater or opera work) with a new cast.

Installation

From npm:

npm install recast

From GitHub:

cd path/to/node_modules
git clone git://github.com/benjamn/recast.git
cd recast
npm install .

Import style

Recast is designed to be imported using named imports:

import { parse, print } from "recast";
console.log(print(parse(source)).code);

import * as recast from "recast";
console.log(recast.print(recast.parse(source)).code);

If you're using CommonJS:

const { parse, print } = require("recast");
console.log(print(parse(source)).code);

const recast = require("recast");
console.log(recast.print(recast.parse(source)).code);

Usage

Recast exposes two essential interfaces, one for parsing JavaScript code (require("recast").parse) and the other for reprinting modified syntax trees (require("recast").print).

Here's a simple but non-trivial example of how you might use .parse and .print:

import * as recast from "recast";

// Let's turn this function declaration into a variable declaration.
const code = [
  "function add(a, b) {",
  "  return a +",
  "    // Weird formatting, huh?",
  "    b;",
  "}"
].join("\n");

// Parse the code using an interface similar to require("esprima").parse.
const ast = recast.parse(code);

Now do whatever you want to ast. Really, anything at all!

See ast-types (especially the def/core.ts) module for a thorough overview of the ast API.

// Grab a reference to the function declaration we just parsed.
const add = ast.program.body[0];

// Make sure it's a FunctionDeclaration (optional).
const n = recast.types.namedTypes;
n.FunctionDeclaration.assert(add);

// If you choose to use recast.builders to construct new AST nodes, all builder
// arguments will be dynamically type-checked against the Mozilla Parser API.
const b = recast.types.builders;

// This kind of manipulation should seem familiar if you've used Esprima or the
// Mozilla Parser API before.
ast.program.body[0] = b.variableDeclaration("var", [
  b.variableDeclarator(add.id, b.functionExpression(
    null, // Anonymize the function expression.
    add.params,
    add.body
  ))
]);

// Just for fun, because addition is commutative:
add.params.push(add.params.shift());

When you finish manipulating the AST, let recast.print work its magic:

const output = recast.print(ast).code;

The output string now looks exactly like this, weird formatting and all:

var add = function(b, a) {
  return a +
    // Weird formatting, huh?
    b;
}

The magic of Recast is that it reprints only those parts of the syntax tree that you modify. In other words, the following identity is guaranteed:

recast.print(recast.parse(source)).code === source

Whenever Recast cannot reprint a modified node using the original source code, it falls back to using a generic pretty printer. So the worst that can happen is that your changes trigger some harmless reformatting of your code.

If you really don't care about preserving the original formatting, you can access the pretty printer directly:

var output = recast.prettyPrint(ast, { tabWidth: 2 }).code;

And here's the exact output:

var add = function(b, a) {
  return a + b;
}

Note that the weird formatting was discarded, yet the behavior and abstract structure of the code remain the same.

Using a different parser

By default, Recast uses the Esprima JavaScript parser when you call recast.parse(code). While Esprima supports almost all modern ECMAScript syntax, you may want to use a different parser to enable TypeScript or Flow syntax, or just because you want to match other compilation tools you might be using.

In order to get any benefits from Recast's conservative pretty-printing, it is very important that you continue to call recast.parse (rather than parsing the AST yourself using a different parser), and simply instruct recast.parse to use a different parser:

const acornAst = recast.parse(source, {
  parser: require("acorn")
});

Why is this so important? When you call recast.parse, it makes a shadow copy of the AST before returning it to you, giving every copied AST node a reference back to the original through a special .original property. This information is what enables recast.print to detect where the AST has been modified, so that it can preserve formatting for parts of the AST that were not modified.

Any parser object that supports a parser.parse(source) method will work here; however, if your parser requires additional options, you can always implement your own parse method that invokes your parser with custom options:

const acornAst = recast.parse(source, {
  parser: {
    parse(source) {
      return require("acorn").parse(source, {
        // additional options
      });
    }
  }
});

To take some of the guesswork out of configuring common parsers, Recast provides several preconfigured parsers, so you can parse TypeScript (for example) without worrying about the configuration details:

const tsAst = recast.parse(source, {
  parser: require("recast/parsers/typescript")
});

Note: Some of these parsers import npm packages that Recast does not directly depend upon, so please be aware you may have to run npm install @babel/parser to use the typescript, flow, or babel parsers, or npm install acorn to use the acorn parser. Only Esprima is installed by default when Recast is installed.

After calling recast.parse, if you're going to transform the AST, make sure that the .original property is preserved. With Babel, for instance, if you call transformFromAST, you must pass cloneInputAst: false in its options. (More detail).

Source maps

One of the coolest consequences of tracking and reusing original source code during reprinting is that it's pretty easy to generate a high-resolution mapping between the original code and the generated code—completely automatically!

With every slice, join, and re-indent-ation, the reprinting process maintains exact knowledge of which character sequences are original, and where in the original source they came from.

All you have to think about is how to manipulate the syntax tree, and Recast will give you a source map in exchange for specifying the names of your source file(s) and the desired name of the map:

var result = recast.print(transform(recast.parse(source, {
  sourceFileName: "source.js"
})), {
  sourceMapName: "source.min.js"
});

console.log(result.code); // Resulting string of code.
console.log(result.map); // JSON source map.

var SourceMapConsumer = require("source-map").SourceMapConsumer;
var smc = new SourceMapConsumer(result.map);
console.log(smc.originalPositionFor({
  line: 3,
  column: 15
})); // { source: 'source.js',
     //   line: 2,
     //   column: 10,
     //   name: null }

Note that you are free to mix and match syntax trees parsed from different source files, and the resulting source map will automatically keep track of the separate file origins for you.

Note also that the source maps generated by Recast are character-by-character maps, so meaningful identifier names are not recorded at this time. This approach leads to higher-resolution debugging in modern browsers, at the expense of somewhat larger map sizes. Striking the perfect balance here is an area for future exploration, but such improvements will not require any breaking changes to the interface demonstrated above.

Options

All Recast API functions take second parameter with configuration options, documented in options.ts

Motivation

The more code you have, the harder it becomes to make big, sweeping changes quickly and confidently. Even if you trust yourself not to make too many mistakes, and no matter how proficient you are with your text editor, changing tens of thousands of lines of code takes precious, non-refundable time.

Is there a better way? Not always! When a task requires you to alter the semantics of many different pieces of code in subtly different ways, your brain inevitably becomes the bottleneck, and there is little hope of completely automating the process. Your best bet is to plan carefully, buckle down, and get it right the first time. Love it or loathe it, that's the way programming goes sometimes.

What I hope to eliminate are the brain-wasting tasks, the tasks that are bottlenecked by keystrokes, the tasks that can be expressed as operations on the syntactic structure of your code. Specifically, my goal is to make it possible for you to run your code through a parser, manipulate the abstract syntax tree directly, subject only to the constraints of your imagination, and then automatically translate those modifications back into source code, without upsetting the formatting of unmodified code.

And here's the best part: when you're done running a Recast script, if you're not completely satisfied with the results, blow them away with git reset --hard, tweak the script, and just run it again. Change your mind as many times as you like. Instead of typing yourself into a nasty case of RSI, gaze upon your new wells of free time and ask yourself: what next?

recast's People

Contributors

alangpierce avatar benjamn avatar brendanannable avatar briandipalma avatar brieb avatar coderaiser avatar conartist6 avatar cpojer avatar cspotcode avatar deoxxa avatar dependabot[bot] avatar djpohly avatar ethancrook99 avatar eventualbuddha avatar fkling avatar greenkeeper[bot] avatar greenkeeperio-bot avatar henryqdineen avatar hzoo avatar keyz avatar mdebbar avatar moroine avatar nene avatar sophiebits avatar thorn0 avatar vjeux avatar vslinko avatar weswigham avatar zertosh avatar zxbodya 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  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

recast's Issues

Write a README

Because "JavaScript syntax tree transformer and conservative pretty-printer" doesn't exactly explain everything.

Improve handling of tab characters

At the moment, each tab is converted to four spaces in lib/parser.js, which isn't even really correct except for tabs that begin at offsets that are a multiple of four.

It would be ideal to reuse existing whitespace wherever possible, if only to avoid making superficial changes to parts of the source that were not altered.

Any lines that differ in more than whitespace can be printed however is easiest.

Allow Visitors to visit abstract types, like Expression, Declaration, or Statement

Currently you can define visitor methods like visitForStatement that will be matched whenever a node with .type === "ForStatement" is encountered, but there's no way (short of overriding and reimplementing genericVisit) to define a method like visitStatement.

Ideally, you could have a method like visitStatement together with methods like visitWhileStatement, and the most specific one would be preferred. So WhileStatements would be handled by visitWhileStatement but all other statements would be handled by visitStatement.

One cool possibility that would arise from this change would be that genericVisit could be renamed/aliased to visitNode.

Incorrect `names` in generated source map

> ast = recast.parse('var abc = 1', {sourceFileName: '1.js'})
> recast.print(ast, {sourceMapName: '1.js.map'}).map.names
[ 'v',
  'a',
  'r',
  'b',
  'c',
  '=',
  '1' ]
  • Reserved keywords should not appear in names field of source map.
  • Real identifier names should not be split into individual characters.

Convert Syntax.* cases to string literals in lib/printer.js

When a property is undefined on Syntax, the case for that type will match any undefined node.type. That seems like an invitation for bizarro clowntown times.

The matching behavior would be much more predictable (and potentially faster) if all the case values were just string literals.

Support source maps

Support for source maps needs to be baked into the Lines abstraction. Mappings can come from the original, complete Lines object, or from replacements made by the Patcher abstraction. Every Lines object should have a private, immutable set of mappings, and when Lines are sliced and joined those mappings should get merged appropriately, so that the final Lines object has a maximal set of mappings back to the original source.

Make number of spaces per tab configurable

Right now it's generally assumed in various places to be four.

Indentation could potentially be inferred by the Parser, but not in pathological cases.

Primarily it's the Printer that needs to know what indentation to use, since that's the only code that introduces its own indentation.

Don't barf on '\r' characters used for indentation

Stack trace:

assert.js:102
  throw new assert.AssertionError({
        ^
AssertionError: "unexpected whitespace character"  "\r"
    at countSpaces (/Users/benjamn/node_modules/recast/lib/lines.js:85:20)
    at /Users/benjamn/node_modules/recast/lib/lines.js:111:21
    at Array.map (native)
    at Object.fromString (/Users/benjamn/node_modules/recast/lib/lines.js:107:46)
    at new Parser (/Users/benjamn/node_modules/recast/lib/parser.js:11:36)
    at exports.run (/Users/benjamn/node_modules/recast/main.js:13:22)
    at fs.readFile (fs.js:176:14)
    at Object.oncomplete (fs.js:297:15)

Reprint slightly modified sequences of statements more intelligently

Currently, if you change the number of statements at the top level of a file, all the whitespace between top-level statements gets reprinted according to the rules of the pretty printer, which isn't the worst thing in the world, but it does generate unnecessary changes.

We should do a better job of surgically replacing old statements with new statements without altering the surrounding whitespace so much, although it would be ideal if completely new statements introduced an appropriate amount of whitespace.

Depend on standard NPM Esprima instead of my own fork

I haven't actually needed to deviate from upstream with my fork of Esprima, and NPM can't always clone the repo from github.com (even though it could install esprima from NPM).

If/when the tests pass with the default version of Esprima installed via npm install esprima, let's just use that.

Fix AssertionError when running examples/identity on underscore.js

Using Underscore version 1.4.2:
http://underscorejs.org/underscore.js

Stack trace:

assert.js:102
  throw new assert.AssertionError({
        ^
AssertionError: false == true
    at pushSlice (/Users/benjamn/node_modules/recast/lib/patcher.js:34:20)
    at Patcher.self.get (/Users/benjamn/node_modules/recast/lib/patcher.js:50:9)
    at self.getReprinter (/Users/benjamn/node_modules/recast/lib/parser.js:63:28)
    at print (/Users/benjamn/node_modules/recast/lib/printer.js:42:20)
    at self.getReprinter (/Users/benjamn/node_modules/recast/lib/parser.js:59:21)
    at Array.forEach (native)
    at self.getReprinter (/Users/benjamn/node_modules/recast/lib/parser.js:55:22)
    at print (/Users/benjamn/node_modules/recast/lib/printer.js:42:20)
    at self.getReprinter (/Users/benjamn/node_modules/recast/lib/parser.js:59:21)
    at Array.forEach (native)

Fix line-endings-dependent tests for non-*NIX systems

......................................................

52 passing (8s)
2 failing

1) lines EachPos:
   AssertionError: 388 === 393
    at testEachPosHelper (D:\Docs\Projects\Web\recast\test\lines.js:73:12)
    at exports.testEachPos (D:\Docs\Projects\Web\recast\test\lines.js:89:5)
    at Context.<anonymous> (D:\Docs\Projects\Web\recast\test\run.js:14:21)
    at Test.Runnable.run (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runnable.js:194:15)
    at Runner.runTest (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:372:10)
    at D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:448:12
    at next (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:297:14)
    at D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:307:7
    at next (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:245:23)
    at Object._onImmediate (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:274:5)
    at processImmediate [as _immediateCallback] (timers.js:330:15)

2) lines CharAt:

    + expected - actual

    +<CR>
    -

    at Lines.compare (D:\Docs\Projects\Web\recast\test\lines.js:108:16)
    at Lines.Lp.eachPos (D:\Docs\Projects\Web\recast\lib\lines.js:497:17)
    at exports.testCharAt (D:\Docs\Projects\Web\recast\test\lines.js:113:11)
    at Context.<anonymous> (D:\Docs\Projects\Web\recast\test\run.js:14:21)
    at Test.Runnable.run (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runnable.js:194:15)
    at Runner.runTest (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:372:10)
    at D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:448:12
    at next (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:297:14)
    at D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:307:7
    at next (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:245:23)
    at Object._onImmediate (D:\Docs\Projects\Web\recast\node_modules\mocha\lib\runner.js:274:5)
    at processImmediate [as _immediateCallback] (timers.js:330:15)

Extract the lib/visitor.js into a separate project

Moving https://github.com/benjamn/ast-types into its own project has worked out very nicely, particularly since it's now easier to distinguish Recast-specific issues from ast-types-specific issues. Let's do the same thing with recast("visitor").Visitor.

Recast does not care what tool you use to manipulate the AST returned by require("recast").parse, so it might be wise to avoid the appearance of preferring one visitor/transformer tool over others.

Note that lib/visitor.js is the only consumer of lib/Class.js, which probably should also be spun off into its own project.

Can't install; npm fails with 404 for ast-types 0.3.10

npm install recast --save-dev

npm http GET https://registry.npmjs.org/ast-types/-/ast-types-0.3.10.tgz
npm http 404 https://registry.npmjs.org/ast-types/-/ast-types-0.3.10.tgz
npm ERR! fetch failed https://registry.npmjs.org/ast-types/-/ast-types-0.3.10.tgz
npm ERR! Error: 404 Not Found
npm ERR!     at WriteStream.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/fetch.js:57:12)
npm ERR!     at WriteStream.EventEmitter.emit (events.js:117:20)
npm ERR!     at fs.js:1596:14
npm ERR!     at Object.oncomplete (fs.js:107:15)
npm ERR! If you need help, you may report this log at:
npm ERR!     <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR!     <[email protected]>

npm ERR! System Darwin 13.0.0
npm ERR! command "node" "/usr/local/bin/npm" "install" "recast" "--save-dev"
npm ERR! cwd /Users/aylott/Projects/Facebook/react
npm ERR! node -v v0.10.5
npm ERR! npm -v 1.2.18
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /Users/aylott/Projects/Facebook/react/npm-debug.log
npm ERR! not ok code 0

When parsing, augment SourceLocation objects with a "lines" property

This addition will give meaning to the start and end properties of the location object.

The value of the lines property will be a reference to the Lines object that was created by the Parser.

If we add the lines property to the original AST, it will be copied over to the copy returned by parser.getAst(), provided we make the property enumerable. Not sure if this is the behavior we want, though. Maybe make the lines property non-enumerable, to keep it somewhat out of sight?

The subtle benefit of this change is that the Printer will no longer have to know anything about the Parser in order to conservatively reprint a node, and we can move getReprinter into lib/patcher.js.

Finish writing test/comments.js

Comment handling deserves to be tested, especially because comment support is a relatively recent addition to the esprima parser, and having tests would allow us to upgrade esprima with confidence that its comment support is still compatible with our needs.

Use .indentTail more widely in lib/printer.js instead of assuming that children print as a single line

For instance, we currently assume that the parameters of a function will all be identifiers, which implies that they will not contain line breaks, but we might need to print a comment on the line before a parameter name, or we might eventually support destructuring parameter syntax, which could span multiple lines.

The Lines.prototype.indentTail function will have no effect on Lines objects that are not multi-line (in fact, because Lines objects are immutable, it will just return the original object). However, it needs to be called whenever a multi-line child would need to be indented.

Address `getIndent` TODO in lib/patcher.js

The appropriate indentation for a node is not necessarily the amount of leading whitespace on the line where the node starts, as this code naïvely assumes:

function getIndent(node) {
    // TODO Improve this.
    return lines.getIndentAt(node.loc.start.line);
}

The correct indentation for a node is something closer to the amount of leading whitespace on the line where the nearest Statement ancestor of the node begins.

Preserve trailing comments that will not otherwise be reprinted

When the Printer falls back to printing a node generically, any comments that fall within the node but after the last child of the node will not be reprinted. Though rare, such comments certainly should be preserved if possible.

This should be a simple matter of associating trailing comments with the previous child in require("comments").add, and making sure to print them in printWithComments (in lib/parser.js).

Allow tests to be run individually by mocha

Ideally mocha test/lines.js would produce output similar to the "lines" section of the npm test output. This used to work with whiskey, and would be nice if it worked again.

To be clear, I have no problem with sacrificing features like this temporarily to make progress quickly. Switching to mocha was clearly a good move.

Support input source maps

Right now, Recast supports generating completely new source maps, assuming the code parsed by recast.parse is the original source code. If you're using Recast for your whole build pipeline, this might be a reasonable assumption, but it's easy to imagine situations where you use another tool earlier in the build process that generates its own source map, and you want to compose that source map with the one produced by Recast to get one complete source map.

Since this is similar to UglifyJS's --in-source-map option, a good interface might be options.inSourceMap or options.inputSourceMap.

I think Lines.prototype.getSourceMap can be left alone, so that it continues to return a new source map, and then Printer.prototype.print can call lines.getSourceMap and compose the result with options.inputSourceMap.

Examples have been broken for a while

For example:

~/node_modules/recast% example/add-braces main.js 

/Users/benjamn/node_modules/recast/main.js:35
    var writeback = options.writeback || defaultWriteback;
                           ^
TypeError: Cannot read property 'writeback' of undefined
    at runString (/Users/benjamn/node_modules/recast/main.js:35:28)
    at /Users/benjamn/node_modules/recast/main.js:26:9
    at fs.js:266:14
    at Object.oncomplete (fs.js:107:15)

Flesh out lib/builder.js

The most annoying and error-prone part of manipulating abstract syntax trees is creating new nodes with appropriate child nodes.

The code in lib/builder.js represents the bare minimum that was needed to write certain kinds of transformations, but ideally it should support the entire Mozilla Parser API:
https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API

The best approach to testing lib/builder.js would be to add strict type assertions everywhere possible and then just be sure to exercise each builder function.

Allow main.js to process multiple files and/or directories

This can be done in parallel, since the processing of one file is unaffected by the processing of any others.

The functional semantics of the processing would have to change, though: instead of simply printing the new contents of the file, the script would need to overwrite the original files with their new contents.

A tool like git add -p could be used to sort through the changes afterward.

This might even be preferable, because not everyone knows how to replace the contents of a file using a tool like sponge or similar.

Future-proof require("recast").print by returning a { code: code, ... } result object

When we support source maps (#43), it will be convenient to add another property called map to the result object.

This will require a minor version bump, obviously. Other breaking changes to the API should happen at the same time.

To smooth the transition, the result object should probably have a toString method that returns the printed source but writes a warning to process.stderr.

Develop a suite of given/expected tests

These tests could be organized into directories named after the purpose of the test. For example:

test/
    consoleLogRemoval/
        given.js
        visitor.js
        expected.js

The test/cosoleLogRemoval/visitor.js file should be a module that exports a visitor property that can be used to transform the given code into the expected code. In this case, one might imagine the visitor would remove CallExpressions involving console.log.

The results can be evaluated in two phases. First we should check that the AST of the expected code is equivalent to the AST produced by the visitor. Then the modified AST should be reprinted and the resulting code should be compared against the contents of expected.js.

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.