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

feat: expose reporter api #41

Merged
merged 4 commits into from
Aug 14, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'plugin:eslint-plugin/recommended',
'prettier'
],
env: { mocha: true },
root: true,
rules: {
'prettier/prettier': ['error', { singleQuote: true }],
Expand Down
187 changes: 118 additions & 69 deletions eslint-plugin-prettier.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const FB_PRETTIER_OPTIONS = {

const LINE_ENDING_RE = /\r\n|[\r\n\u2028\u2029]/;

const OPERATION_INSERT = 'insert';
const OPERATION_DELETE = 'delete';
const OPERATION_REPLACE = 'replace';

// ------------------------------------------------------------------------------
// Privates
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -104,18 +108,13 @@ function showInvisibles(str) {
return ret;
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/**
* Reports issues where the context's source code differs from the Prettier
formatted version.
* @param {RuleContext} context - The ESLint rule context.
* Generate results for differences between source code and formatted version.
* @param {string} source - The original source.
* @param {string} prettierSource - The Prettier formatted source.
* @returns {void}
* @returns {Array} - An array contains { operation, offset, insertText, deleteText }
*/
function reportDifferences(context, prettierSource) {
function generateDifferences(source, prettierSource) {
// fast-diff returns the differences between two texts as a series of
// INSERT, DELETE or EQUAL operations. The results occur only in these
// sequences:
Expand All @@ -130,8 +129,8 @@ function reportDifferences(context, prettierSource) {
// and another's beginning does not have line endings (i.e. issues that occur
// on contiguous lines).

const source = context.getSourceCode().text;
const results = diff(source, prettierSource);
const differences = [];

const batch = [];
let offset = 0; // NOTE: INSERT never advances the offset.
Expand Down Expand Up @@ -166,6 +165,8 @@ function reportDifferences(context, prettierSource) {
}
}

return differences;

function flush() {
let aheadDeleteText = '';
let aheadInsertText = '';
Expand All @@ -187,16 +188,33 @@ function reportDifferences(context, prettierSource) {
}
}
if (aheadDeleteText && aheadInsertText) {
reportReplace(context, offset, aheadDeleteText, aheadInsertText);
differences.push({
offset,
operation: OPERATION_REPLACE,
insertText: aheadInsertText,
deleteText: aheadDeleteText
});
} else if (!aheadDeleteText && aheadInsertText) {
reportInsert(context, offset, aheadInsertText);
differences.push({
offset,
operation: OPERATION_INSERT,
insertText: aheadInsertText
});
} else if (aheadDeleteText && !aheadInsertText) {
reportDelete(context, offset, aheadDeleteText);
differences.push({
offset,
operation: OPERATION_DELETE,
deleteText: aheadDeleteText
});
}
offset += aheadDeleteText.length;
}
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/**
* Reports an "Insert ..." issue where text must be inserted.
* @param {RuleContext} context - The ESLint rule context.
Expand Down Expand Up @@ -268,68 +286,99 @@ function reportReplace(context, offset, deleteText, insertText) {
// Module Definition
// ------------------------------------------------------------------------------

module.exports.rules = {
prettier: {
meta: {
fixable: 'code',
schema: [
// Prettier options:
{
anyOf: [
{ enum: [null, 'fb'] },
{ type: 'object', properties: {}, additionalProperties: true }
]
},
// Pragma:
{ type: 'string', pattern: '^@\\w+$' }
]
},
create(context) {
const prettierOptions = context.options[0] === 'fb'
? FB_PRETTIER_OPTIONS
: context.options[0];

const pragma = context.options[1]
? context.options[1].slice(1) // Remove leading @
: null;
module.exports = {
showInvisibles,
generateDifferences,
rules: {
prettier: {
meta: {
fixable: 'code',
schema: [
// Prettier options:
{
anyOf: [
{ enum: [null, 'fb'] },
{ type: 'object', properties: {}, additionalProperties: true }
]
},
// Pragma:
{ type: 'string', pattern: '^@\\w+$' }
]
},
create(context) {
const prettierOptions = context.options[0] === 'fb'
? FB_PRETTIER_OPTIONS
: context.options[0];

const sourceCode = context.getSourceCode();
const source = sourceCode.text;
const pragma = context.options[1]
? context.options[1].slice(1) // Remove leading @
: null;

// The pragma is only valid if it is found in a block comment at the very
// start of the file.
if (pragma) {
// ESLint 3.x reports the shebang as a "Line" node, while ESLint 4.x
// reports it as a "Shebang" node. This works for both versions:
const hasShebang = source.startsWith('#!');
const allComments = sourceCode.getAllComments();
const firstComment = hasShebang ? allComments[1] : allComments[0];
if (
!(firstComment &&
firstComment.type === 'Block' &&
firstComment.loc.start.line === (hasShebang ? 2 : 1) &&
firstComment.loc.start.column === 0)
) {
return {};
}
const parsed = docblock.parse(firstComment.value);
if (parsed[pragma] !== '') {
return {};
}
}
const sourceCode = context.getSourceCode();
const source = sourceCode.text;

return {
Program() {
if (!prettier) {
// Prettier is expensive to load, so only load it if needed.
prettier = require('prettier');
// The pragma is only valid if it is found in a block comment at the very
// start of the file.
if (pragma) {
// ESLint 3.x reports the shebang as a "Line" node, while ESLint 4.x
// reports it as a "Shebang" node. This works for both versions:
const hasShebang = source.startsWith('#!');
const allComments = sourceCode.getAllComments();
const firstComment = hasShebang ? allComments[1] : allComments[0];
if (
!(firstComment &&
firstComment.type === 'Block' &&
firstComment.loc.start.line === (hasShebang ? 2 : 1) &&
firstComment.loc.start.column === 0)
) {
return {};
}
const prettierSource = prettier.format(source, prettierOptions);
if (source !== prettierSource) {
reportDifferences(context, prettierSource);
const parsed = docblock.parse(firstComment.value);
if (parsed[pragma] !== '') {
return {};
}
}
};

return {
Program() {
if (!prettier) {
// Prettier is expensive to load, so only load it if needed.
prettier = require('prettier');
}
const prettierSource = prettier.format(source, prettierOptions);
if (source !== prettierSource) {
const differences = generateDifferences(source, prettierSource);

differences.forEach(difference => {
switch (difference.operation) {
case OPERATION_INSERT:
reportInsert(
context,
difference.offset,
difference.insertText
);
break;
case OPERATION_DELETE:
reportDelete(
context,
difference.offset,
difference.deleteText
);
break;
case OPERATION_REPLACE:
reportReplace(
context,
difference.offset,
difference.deleteText,
difference.insertText
);
break;
}
});
}
}
};
}
}
}
};
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"jest-docblock": "^20.0.1"
},
"peerDependencies": {
"eslint": ">=3.14.1",
"prettier": ">= 0.11.0"
},
"devDependencies": {
Expand Down
32 changes: 31 additions & 1 deletion test/prettier.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@

const fs = require('fs');
const path = require('path');
const assert = require('assert');

const rule = require('..').rules.prettier;
const eslintPluginPrettier = require('..');

const rule = eslintPluginPrettier.rules.prettier;
const RuleTester = require('eslint').RuleTester;

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -64,6 +67,33 @@ ruleTester.run('prettier', rule, {
].map(loadInvalidFixture)
});

describe('generateDifferences', () => {
it('operation: insert', () => {
const differences = eslintPluginPrettier.generateDifferences(
'abc',
'abcdef'
);
assert.deepEqual(differences, [
{ operation: 'insert', offset: 3, insertText: 'def' }
]);
});
it('operation: delete', () => {
const differences = eslintPluginPrettier.generateDifferences(
'abcdef',
'abc'
);
assert.deepEqual(differences, [
{ operation: 'delete', offset: 3, deleteText: 'def' }
]);
});
it('operation: replace', () => {
const differences = eslintPluginPrettier.generateDifferences('abc', 'def');
assert.deepEqual(differences, [
{ operation: 'replace', offset: 0, deleteText: 'abc', insertText: 'def' }
]);
});
});

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
Expand Down