Created
April 17, 2017 23:14
-
-
Save nsfmc/849fbf50bb264cfac7d383d41543f37d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import {parse} from 'babylon'; | |
const commonParser = (input: string) => parse(input, { | |
sourceType: 'module', | |
plugins: ['jsx', 'flow'], | |
}); | |
const bodyParser = (input: string) => commonParser(input).program.body; | |
const commentParser = (input: string) => commonParser(input).comments; | |
const parseLine = (input: string) => bodyParser(input).filter( | |
d => d.type === 'ImportDeclaration' | |
)[0]; | |
const typeofImportDeclaration = (declaration: any) => { | |
const value = declaration.source.value; | |
const src = value.startsWith('src/'); | |
const relative = value.search(/^\.+/) !== -1; | |
const cssPath = value.endsWith('.css'); | |
const jsxPath = value.endsWith('.jsx'); | |
const componentPath = value.search('components/') !== -1; | |
const flowImport = declaration.importKind === 'type'; | |
if (flowImport) { | |
return 'type'; | |
} | |
if (!src && !relative) { | |
return 'vendor'; | |
} | |
if (cssPath) { | |
return 'css'; | |
} | |
if (jsxPath || componentPath) { | |
return 'component'; | |
} | |
if (src && !componentPath) { | |
return 'misc'; | |
} | |
}; | |
export const kindOf = (importString: string) => ( | |
typeofImportDeclaration(parseLine(importString)) | |
); | |
export const highLevelParser = (codeBlock: string) => { | |
const declarations = bodyParser(codeBlock).map(declaration => { | |
const startLine = declaration.loc.start.line; | |
const endLine = declaration.loc.end.line; | |
switch (declaration.type) { | |
case 'ImportDeclaration': | |
return [typeofImportDeclaration(declaration), [startLine, endLine]]; | |
default: | |
return ['other', [startLine, endLine]]; | |
} | |
}); | |
const comments = commentParser(codeBlock).map(comment => { | |
const startLine = comment.loc.start.line; | |
const endLine = comment.loc.start.line; | |
return ['comment', [startLine, endLine], comment.value]; | |
}); | |
return [...declarations, ...comments].sort((a, b) => a[1][0] - b[1][0]); | |
}; | |
export const isSpacedOk = (simpleParsed: any[]) => { | |
const pragmaFirst = pragmaSpaced(simpleParsed); | |
const twoSpaces = doubleSpacedContent(simpleParsed); | |
return pragmaFirst && twoSpaces; | |
}; | |
const doubleSpacedContent = (simpleParsed: any[]) => { | |
// Ensure there are two spaces between import country and code land | |
const firstNonCommentLine = simpleParsed.findIndex(node => node[0] === 'other'); | |
if (firstNonCommentLine > 0) { | |
const lastNonCodeDeclaration = simpleParsed[firstNonCommentLine - 1]; | |
const lastImportishEndLine = lastNonCodeDeclaration[1][1]; | |
const firstNonImportStartLine = simpleParsed[firstNonCommentLine][1][0]; | |
return firstNonImportStartLine - lastImportishEndLine === 3; | |
} | |
return true; // pure vanilla js, don't care; | |
}; | |
const pragmaSpaced = (simpleParsed: any[]) => { | |
// ensure that there is an empty line between pragma and non-pragma | |
const firstNonComment = simpleParsed.findIndex(node => node[0] !== 'comment' && node[0] !== 'other'); | |
const pragma = simpleParsed.findIndex(node => { | |
const [type, loc, value] = node; | |
const isComment = type === 'comment'; | |
if (isComment) { | |
const isFlowPragma = value.search('@flow') !== -1; | |
return isFlowPragma; | |
} | |
return false; | |
}); | |
const hasPragma = pragma > -1; | |
const pragmaFirst = hasPragma && pragma < firstNonComment; | |
if (pragmaFirst) { | |
const pragmaLine = simpleParsed[pragma][1][1]; | |
const nextLine = simpleParsed[firstNonComment][1][0]; | |
return nextLine - pragmaLine === 2; | |
} | |
return true; // no pragma, don't care | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import {kindOf, highLevelParser, isSpacedOk} from '../imports'; | |
describe('detecting vendor imports', () => { | |
test('looks like a vendor import', () => { | |
expect(kindOf(`import React from 'react';`)).toEqual('vendor'); | |
}); | |
test('looks like a destructured vendor import', () => { | |
expect(kindOf(`import {Component} from 'react';`)).toEqual('vendor'); | |
}); | |
test('deals with lodash and other nested imports', () => { | |
expect(kindOf(`import zip from 'lodash/zip';`)).toEqual('vendor'); | |
}); | |
}); | |
describe('detects flow type imports', () => { | |
test('looks like a flow type import', () => { | |
expect(kindOf(`import type {CSS} from './style.css';`)).toEqual('type'); | |
}); | |
}); | |
describe('detects css imports', () => { | |
test('looks like a css import', () => { | |
expect(kindOf(`import css from './style.css';`)).toEqual('css'); | |
}); | |
}); | |
describe('detects component imports', () => { | |
test('looks like a component import', () => { | |
expect(kindOf(`import Selector from 'src/components/lib/Selector/selector.jsx';`)).toEqual('component'); | |
}); | |
test('looks like a component import', () => { | |
expect(kindOf(`import Selector from 'src/components/lib/rte/';`)).toEqual('component'); | |
}); | |
}); | |
describe('detects miscellaneous js imports', () => { | |
test('looks like a utils import', () => { | |
expect(kindOf(`import * as reduxApi from 'src/utils/redux-api.js';`)).toEqual('misc'); | |
}); | |
test('looks like an action-creator import', () => { | |
expect(kindOf(`import * as reduxApi from 'src/action-creators/agency-overvie';`)).toEqual('misc'); | |
}); | |
}); | |
describe('highLevelParser', () => { | |
const codeBlock = ( | |
`// @flow | |
import type {AgencyOverview} from 'src/api-parsers/index'; | |
import flow from 'lodash/flow'; | |
import {key, cached, fetching} from 'src/utils/redux'; | |
import * as reduxApi from 'src/utils/redux-api.js'; | |
import parsers from 'src/api-parsers/index'; // eslint-disable-line no-duplicate-imports | |
import css from './blah.css'; | |
export const FOO = 2; | |
`); | |
test('declarationOrder', () => { | |
expect(highLevelParser(codeBlock)[0]).toEqual(['comment', [1, 1], ' @flow']); | |
}); | |
}); | |
describe('spacing', () => { | |
test('complains about inadequate space between pragma and first import', () => { | |
const parsed = [['comment', [1, 1], ' @flow'], ['type', [2, 2]], ['vendor', [4, 4]]]; | |
expect(isSpacedOk(parsed)).toBeFalsy(); | |
}); | |
test('is ok with one line between pragma and first import', () => { | |
const parsed = [['comment', [1, 1], ' @flow'], ['type', [3, 2]], ['vendor', [4, 4]]]; | |
expect(isSpacedOk(parsed)).toBeTruthy(); | |
}); | |
test('needs two lines between pragma and first non-import node', () => { | |
const parsed = [['comment', [1, 1], ' @flow'], ['other', [4, 2]]]; | |
expect(isSpacedOk(parsed)).toBeTruthy(); | |
}); | |
test('needs two lines between imports and first non-import node', () => { | |
const parsed = [['comment', [1, 1], 'whatever'], ['css', [6, 9]], ['other', [12, 12]]]; | |
expect(isSpacedOk(parsed)).toBeTruthy(); | |
}); | |
test('deals with files without imports', () => { | |
const parsed = [['other', [2, 2]]]; | |
expect(isSpacedOk(parsed)).toBeTruthy(); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment