Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New config lookup #125

Merged
merged 5 commits into from Aug 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .flowconfig
@@ -1,10 +1,11 @@
[ignore]
.*/node_modules/fbjs/.*
.*/node_modules/.*
.*/git/.*
.*/test/codebases/.*
.*/test/.*\.example\.js

[options]
suppress_comment=.*\\$FlowIgnore
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
1 change: 0 additions & 1 deletion appveyor.yml
Expand Up @@ -8,7 +8,6 @@ test_script:
- node --version
- yarn -V
- yarn test
- yarn spec-win

install:
- ps: Install-Product node $env:nodejs_version x64
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -15,7 +15,6 @@
"prettier": "prettier --single-quote --write \"./src/*.js\"",
"preversion": "npm run build",
"spec": "npm run build && jest test/format.spec.js",
"spec-win": "npm run build && jest",
"test": "flow && npm run lint && npm run spec"
},
"repository": {
Expand All @@ -37,6 +36,7 @@
],
"dependencies": {
"babel-runtime": "^6.26.0",
"find-up": "^3.0.0",
"slash": "^2.0.0"
},
"devDependencies": {
Expand All @@ -63,6 +63,7 @@
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-vue": "^4.5.0",
"execa": "^0.10.0",
"flow-bin": "^0.78.0",
"jest-cli": "^23.0.0",
"prettier": "^1.12.1",
Expand Down
1 change: 1 addition & 0 deletions src/collect.js
Expand Up @@ -230,6 +230,7 @@ function spawnFlow(
getFlowBin(),
[mode, '--json', `--root=${root}`, filepath, ...extraOptions],
{
cwd: root,
input,
encoding: 'utf-8'
}
Expand Down
34 changes: 18 additions & 16 deletions src/get-program.js
Expand Up @@ -5,17 +5,16 @@ type Pos = {
column: number
};

type Loc = {
export type Loc = {
start: Pos,
end: Pos
};

type Program = { text: string, loc: Loc, offset: Pos };

export default function( source: Object, node: Object ): ?Program {
export type Program = { text: string, loc: Loc, offset: Pos };

export default function(source: Object, node: Object): ?Program {
// Ignore if body is empty.
if ( node.body.length === 0 ) {
if (node.body.length === 0) {
return;
}

Expand All @@ -27,17 +26,20 @@ export default function( source: Object, node: Object ): ?Program {
// With babel-eslint, program.loc.start.column is always 0,
// workaround it by using the first node of the body to get the offset.

if ( comments0 ) {
start = node.range[0] < comments0.range[0] ? {
line: body0.loc.start.line,
column: body0.loc.start.column
} : {
line: comments0.loc.start.line,
column: comments0.loc.start.column
};
if (comments0) {
start =
node.range[0] < comments0.range[0]
? {
line: body0.loc.start.line,
column: body0.loc.start.column
}
: {
line: comments0.loc.start.line,
column: comments0.loc.start.column
};
range = [
Math.min( node.range[0], comments0.range[0] ),
Math.max( node.range[1], node.comments[node.comments.length - 1].range[1] )
Math.min(node.range[0], comments0.range[0]),
Math.max(node.range[1], node.comments[node.comments.length - 1].range[1])
];
} else {
start = {
Expand All @@ -48,7 +50,7 @@ export default function( source: Object, node: Object ): ?Program {
}

return {
text: source.text.slice( range[0], range[1] ),
text: source.text.slice(range[0], range[1]),
loc: {
start,
end: start
Expand Down
193 changes: 110 additions & 83 deletions src/index.js
Expand Up @@ -2,45 +2,83 @@

import path from 'path';
import fs from 'fs';
// $FlowIgnore
import findUp from 'find-up';
import {
type CollectOutputElement,
FlowSeverity,
collect,
coverage
} from './collect';
import getProgram from './get-program';
import getProgram, { type Program, type Loc } from './get-program';

type EslintContext = {
getAllComments: Function,
getFilename: Function,
getSourceCode: Function,
report: Function,
getAllComments: () => { value: string }[],
getFilename: () => string,
getSourceCode: () => Object,
report: ({ loc: Loc, message: string }) => void,
settings: ?{
'flowtype-errors': ?{
flowDir?: string,
stopOnExit?: any
}
},
options: any[]
};

let runOnAllFiles;
type Info = {
flowDir: string,
program: Program
};

function hasFlowPragma(source) {
return source.getAllComments().some(comment => /@flow/.test(comment.value));
}
const DEFAULT_LOC = {
start: {
line: 1,
column: 0
},
end: {
line: 1,
column: 0
}
};

function lookupInfo(
context: EslintContext,
source: Object,
node: Object
): ?Info {
const flowconfigFile = findUp.sync('.flowconfig', {
cwd: path.dirname(context.getFilename())
});

if (flowconfigFile == null) {
const program = getProgram(source, node);
context.report({
loc: program ? program.loc : DEFAULT_LOC,
message: "Could not find '.flowconfig' file"
});
return null;
}

const flowDir = path.dirname(flowconfigFile);

const runOnAllFiles = fs
.readFileSync(flowconfigFile, 'utf8')
.includes('all=true');

const shouldRun =
runOnAllFiles ||
source.getAllComments().some(comment => /@flow/.test(comment.value));

const program = shouldRun && getProgram(source, node);

if (program) {
return {
flowDir,
program
};
}

function lookupFlowDir(context: EslintContext): string {
const root = process.cwd();
const flowDirSetting: string =
(context.settings &&
context.settings['flowtype-errors'] &&
context.settings['flowtype-errors'].flowDir) ||
'.';

return fs.existsSync(path.join(root, flowDirSetting, '.flowconfig'))
? path.join(root, flowDirSetting)
: root;
return null;
}

function stopOnExit(context: EslintContext): boolean {
Expand All @@ -54,42 +92,28 @@ function stopOnExit(context: EslintContext): boolean {
function errorFlowCouldNotRun(loc) {
return {
loc,
message:
`Flow could not be run. Possible causes include:
message: `Flow could not be run. Possible causes include:
* Running on 32-bit OS (https://github.com/facebook/flow/issues/2262)
* Recent glibc version not available (https://github.com/flowtype/flow-bin/issues/49)
* FLOW_BIN environment variable ${process.env.FLOW_BIN ? 'set incorrectly' : 'not set'}
* FLOW_BIN environment variable ${
process.env.FLOW_BIN ? 'set incorrectly' : 'not set'
}
.`
};
}

function createFilteredErrorRule(filter: (CollectOutputElement) => any) {
function createFilteredErrorRule(filter: CollectOutputElement => any) {
return function showErrors(context: EslintContext) {
return {
Program(node: Object) {
const source = context.getSourceCode();
const flowDir = lookupFlowDir(context);

// Check to see if we should run on every file
if (runOnAllFiles === undefined) {
try {
runOnAllFiles = fs
.readFileSync(path.join(flowDir, '.flowconfig'))
.toString()
.includes('all=true');
} catch (err) {
runOnAllFiles = false;
}
}
const info = lookupInfo(context, source, node);

if (runOnAllFiles === false && !hasFlowPragma(source)) {
if (!info) {
return;
}

const program = getProgram(source, node);
if ( !program ) {
return;
}
const { flowDir, program } = info;

const collected = collect(
program.text,
Expand Down Expand Up @@ -136,50 +160,53 @@ export default {
return {
Program(node: Object) {
const source = context.getSourceCode();
const info = lookupInfo(context, source, node);

if (!info) {
return;
}

const { flowDir, program } = info;

const res = coverage(
program.text,
flowDir,
stopOnExit(context),
context.getFilename()
);

if (res === true) {
return;
}

if (res === false) {
context.report(errorFlowCouldNotRun(program.loc));
return;
}

const requiredCoverage = context.options[0];
const { coveredCount, uncoveredCount } = res;

/* eslint prefer-template: 0 */
const percentage = Number(
Math.round(coveredCount / (coveredCount + uncoveredCount) * 10000) +
'e-2'
);

if (hasFlowPragma(source)) {
const program = getProgram(source, node);
if ( !program ) {
return;
}

const res = coverage(
program.text,
lookupFlowDir(context),
stopOnExit(context),
context.getFilename()
);

if (res === true) {
return;
}

if (res === false) {
context.report(errorFlowCouldNotRun(program.loc));
return;
}

const requiredCoverage = context.options[0];
const { coveredCount, uncoveredCount } = res;

/* eslint prefer-template: 0 */
const percentage = Number(
Math.round(
coveredCount / (coveredCount + uncoveredCount) * 10000
) + 'e-2'
);

if (percentage < requiredCoverage) {
context.report({
loc: program.loc,
message: `Expected coverage to be at least ${requiredCoverage}%, but is: ${percentage}%`
});
}
if (percentage < requiredCoverage) {
context.report({
loc: program.loc,
message: `Expected coverage to be at least ${requiredCoverage}%, but is: ${percentage}%`
});
}
}
};
},
'show-errors': createFilteredErrorRule(({ level }) => level !== FlowSeverity.Warning),
'show-warnings': createFilteredErrorRule(({ level }) => level === FlowSeverity.Warning)
'show-errors': createFilteredErrorRule(
({ level }) => level !== FlowSeverity.Warning
),
'show-warnings': createFilteredErrorRule(
({ level }) => level === FlowSeverity.Warning
)
}
};
4 changes: 4 additions & 0 deletions test/codebases/column-offset/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
4 changes: 4 additions & 0 deletions test/codebases/coverage-fail/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
4 changes: 4 additions & 0 deletions test/codebases/coverage-fail2/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
4 changes: 4 additions & 0 deletions test/codebases/coverage-ok/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
4 changes: 4 additions & 0 deletions test/codebases/coverage-ok2/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
4 changes: 4 additions & 0 deletions test/codebases/flow-pragma-1/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
4 changes: 4 additions & 0 deletions test/codebases/flow-pragma-2/.flowconfig
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable