fullstackio/cq

Name: cq

Owner: Fullstack.io

Description: extract code snippets using selectors (instead of line numbers) - ES5, ES6, TypeScript

Created: 2016-06-20 17:13:08.0

Updated: 2018-04-03 02:49:17.0

Pushed: 2017-04-04 20:04:58.0

Homepage:

Size: 767

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Code Query - extract code snippets using selectors

cq: Code Query npm package Dolpins

A tool to extract code snippets using selectors (instead of line numbers)

Supports JavaScript ES5, ES6, JSX, and TypeScript

Install
m install --global @fullstackio/cq
Usage
 <query> <file>



t file | cq <query>
Examples

Say we have a file examples/basics.js with the following code:

xamples/basics.js
t bye = function() {
turn 'bye';

); // -> 'bye'

Farm = () => 'cow';

s Barn {
nstructor(height, width) {
this.height = height;
this.width = width;


lcArea() {
return this.height * this.width;


Get the bye() function:

 '.bye' examples/basics.js

t bye = function() {
turn 'bye';

Get the calcArea() function on the Barn class:

 '.Barn .calcArea' examples/basics.js

lcArea() {
return this.height * this.width;

Get the bye() function plus the line after:

context(identifier, linesBefore, linesAfter)`
 'context(.bye,0,1)' examples/basics.js

t bye = function() {
turn 'bye';

); // -> 'bye'

Get the range of constructor through calcArea, inclusive, of the Barn class

 '.Barn .constructor-.calcArea' examples/basics.js

nstructor(height, width) {
this.height = height;
this.width = width;


lcArea() {
return this.height * this.width;

If you pass --json you'll get the results in JSON, which can be useful for further processing:

 --json 'context(.bye,0,1)' examples/basics.js

{
  "code": "const bye = function() {\n  return 'bye';\n}\nbye(); // -> 'bye'",
  "start": 598,
  "end": 659,
  "start_line": 25,
  "end_line": 28
}

cq works with TypeScript as well. Say we had the following TypeScript File:

uthService.ts
rt {Injectable, provide} from '@angular/core';

ectable()
rt class AuthService {
gin(user: string, password: string): boolean {
if (user === 'user' && password === 'password') {
  localStorage.setItem('username', user);
  return true;
}

return false;


gout(): any {
localStorage.removeItem('username');


tUser(): any {
return localStorage.getItem('username');


LoggedIn(): boolean {
return this.getUser() !== null;



rt var AUTH_PROVIDERS: Array<any> = [
ovide(AuthService, {useClass: AuthService})

Get the AUTH_PROVIDERS export:

 '.AUTH_PROVIDERS' examples/AuthService.ts

rt var AUTH_PROVIDERS: Array<any> = [
ovide(AuthService, {useClass: AuthService})

Get the isLoggedIn() function through AUTH_PROVIDERS

 '(.AuthService .isLoggedIn)-.AUTH_PROVIDERS' examples/AuthService.ts

LoggedIn(): boolean {
return this.getUser() !== null;



rt var AUTH_PROVIDERS: Array<any> = [
ovide(AuthService, {useClass: AuthService})

cq can search for strings as well as identifiers. Say we have the following test:

rt chai from 'chai';
t assert = chai.assert;

ribe('My First Test', () => {
('basic assert', () => {
assert.equal(1, 1);
;


ribe('My Second Test', () => {
('basic assert', () => {
// this is the second one
assert.equal(2, 2);
;

We can get the first test:

 "'My First Test'" examples/mocha.test.js

ribe('My First Test', () => {
('basic assert', () => {
assert.equal(1, 1);
;

Or get the it block in the second test:

 "'My Second Test' 'basic assert'" examples/mocha.test.js

('basic assert', () => {
// this is the second one
assert.equal(2, 2);
;

Sometimes we want to pull the comments before a selection. cq supports this using the comments() operator:

omments.js
tion hello() {
turn 'hi';



function bye

tion bye() {
turn 'see ya';

Get the bye() function with comments:

 'comments(.bye)' comments.js


function bye

tion bye() {
turn 'see ya';

This file was itself generated using cq.

See many more examples in the /examples directory

Features
Operators

cq supports a number of operators that modify the selection:

Motivation

When writing blog posts, tutorials, and books about programming there's a tension between code that gets copied and pasted into the text and runnable code on disk.

If you copy and paste your code into the copy, then you're prone to typos, missing steps. When things change, you have to update all of the copypasta and eyeball it to make sure you didn't miss anything. Mistakes are really easy to make because you can't really test code that's in your manuscript without it's context.

A better solution is to keep your code (or steps of your code) as runnable examples on disk. You can then load the code into your manuscript with some pre-processing.

The problem with the code-on-disk approach is how to designate the ranges of code you wish to import. Line numbers are the most obvious approach, but if you add or remove a line of code, then you have to adjust all line numbers accordingly.

cq is a tool that lets you specify selectors to extract portions of code. Rather than using brittle line numbers, instead cq lets you query your code. It uses babylon to understand the semantics of your code and will extract the appropriate lines.

Query Grammar
.Identifier

Examples:

A dot . preceding JavaScript identifier characters represents an identifier.

In this code:

t Simple = React.createClass({
nder() {
return (
  <div>
    {this.renderName()}
  </div>
)


The query .Simple would find the whole const Simple = ... variable declaration.

Searches for identifiers traverse the whole tree, relative to the parent, and return the first match. This means that you do not have to start at the root. In this case you could query for .render and would receive the render() function. That said, creating more specific queries can help in the case where you want to disambiguate.

[space]

Examples:

The space in a query selection expression designates a parent for the next identifier. For instance, the query .Simple .render will first look for the identifier Simple and then find the render function that is a child of Simple.

The space indicates to search for the next identifier anywhere within the parent. That is, it does not require that the child identifier be a direct child the parent.

In this way the space is analogous to the space in a CSS selector. E.g. search for any child that matches. cq does not yet support the > notation (which would require the identifier to be a direct child), but we may in the future.

You can write child selection in parenthesis () if there is ambiguity. E.g.: (.foo .bar) .

Range

Examples:

Given:

s Barn {
nstructor(height, width) {
this.height = height;
this.width = width;


lcArea() {
return this.height * this.width;


A pair of selections (e.g. identifiers) joined by a dash - form a range. A range will emit the code from the beginning of the match of the first identifier to the end of the match of the last.

You can use a parent identifier to limit the scope of the search of the range as in the query: .Barn .constructor-.calcArea

If you'd like to specify a line number, you can use a number (instead of an identifier) in a range. For example the query: 30-35 will give lines 30 through 35, inclusive.

If you want to specify a child selector at the end of a range, use parenthesis as in this query: 1-(.AuthService .login). The previous query will return the lines from line 1 to the end of the login() function on AuthService.

You can use the special line number EOF to select until the end-of-file.

'String'

Examples:

You can use a single-quoted string as a selection and cq will search for that string. When a string is found, cq will emit the statement / block associated with that string.

For instance, given:

ribe('My First Test', () => {
('basic assert', () => {
assert.equal(1, 1);
;

You could search for the strings 'My First Test' or 'basic assert' and receive the appropriate selection.

Operators

Examples:

Given:

ere is the bye function
t bye = function() {
turn 'bye';

); // -> 'bye'

Operators allow you to change the result of the inner selection.

context()

The context() operation takes line numbers before and after the selection. For example, context(.foo, 2, 2) will give two lines before and two lines after the .foo node.

Keep in mind that the selection denotes a node which can span multiple lines. With that in mind, positive numbers “expand” the selection and negative numbers “contract”. That is, if numLinesBeforeStart is negative, then it can be interpreted as moving the start forward (increasing line numbers). Similarly, if numLinesAfterEnd is negative, the end is moved backwards (decreasing line numbers, towards the top of the document).

context() modifies the range that would be returned from selection. If you'd like to specify a specific number of lines range relative to a selection, then see the window() operator.

window()

window() returns a specific number of lines relative to selection. For example, window(.foo, 0, 4) would give 5 lines, the foo identifier and the four lines following.

It differs from context() in that both arguments to window() are relative to the start of the selection.

window() is useful for extracting a specific range of lines near a particular selection. The selection is considered to start at index 0, which means negative numbers denote the lines before the start of the selection.

If reverse is true, start the window at the end of the selection.

firstLineOf()

Sugar - same as window(selection, 0, 0)

lastLineOf()

Sugar - same as window(selection, 0, 0, true)

upto()

The upto() operation will return the code up-to, but not including, the selection. A convenient (but potentially confusing) default is that the upto() operation trims whitespace. This is normally what you want, but you have to be careful when using upto() and context() together (because upto() may trim lines).

choose()

It's possible for a selection to match more than one node. While you can often disambiguate with child selections, the choose() operator lets you specify a particular match by index.

matchIdx starts at 0. Without the choose operator, the default behavior of any selection is: choose(selection, 0). Say you had two instances of the identifier .foo then you could grab the second by using choose(.foo, 1).

choose can be a bit brittle in that it specifies a specific matchIdx. A potentially better choice is the after() operator which finds the first selection that occurs after a companion selector.

after()

after finds the first selection that occurs after afterSelection.

comments()

The comments() operation will return the selection plus the leading comments before the selection.

decorators()

The decorators() operation will return the selection plus the decorators.

Say we have the following code:

rt { Component } from '@angular/core';

ponent({
lector: 'home',
mplate: `<h1>Welcome!</h1>`

rt class HomeComponent {

When we grab the selection .HomeComponent we'll get just the class

 '.HomeComponent' examples/HomeComponent.ts

rt class HomeComponent {

We use decorators() to get the whole thing:

 'decorators(.HomeComponent)' examples/HomeComponent.ts

ponent({
lector: 'home',
mplate: `<h1>Welcome!</h1>`

rt class HomeComponent {

One thing to keep in mind is that decorations are actually considered children of the node they are attached to. The @Component decoration is also an identifier. This means we get the @Component decoration by itself like this:

 '.HomeComponent .Component' examples/HomeComponent.ts

ponent({
lector: 'home',
mplate: `<h1>Welcome!</h1>`

Other Features
Multiple Queries with Gap Filling

You can have multiple queries and any if they are not contiguous they can be filled with a gap filler:

 '(firstLineOf(.AuthService),.logout,.isLoggedIn,lastLineOf(.AuthService))' examples/AuthService.ts

rt class AuthService {
 ...
gout(): any {
localStorage.removeItem('username');

 ...
LoggedIn(): boolean {
return this.getUser() !== null;


This gap filler can be customized with the --gapFiller option on the commandline.

CLI Usage

To pre-process your markdown on the CLI use the cqmd utility.

Library Usage
cq = require('@fullstackio/cq').default;
results = cq(codeString, query);
ole.log(results.code);
Future
Limitations
Query API Stability

The query API may change (see Future). Any breaking API changes (query or otherwise) will result in a major version bump.

Contributing

Please feel free to submit pull requests!

Authors

Originally written by Nate Murray.

Related
Dependencies
Fullstack React Book

Fullstack React Book

This repo was written and is maintained by the Fullstack React team. If you're looking to learn React, there's no faster way than by spending a few hours with the Fullstack React book.

License

MIT


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.