Skip to content

Commit

Permalink
feat: support printWidth and optionally inherit from prettier config
Browse files Browse the repository at this point in the history
  • Loading branch information
tylerturdenpants committed Jun 29, 2020
1 parent bdb4f94 commit e3e621d
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 41 deletions.
158 changes: 118 additions & 40 deletions lib/rules/decorator-position.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
'use strict';

const { CLIEngine } = require('eslint');

const cli = new CLIEngine();

module.exports = {
meta: {
type: 'layout',
Expand Down Expand Up @@ -50,6 +54,9 @@ module.exports = {
type: 'object',
additionalProperties: false,
properties: {
printWidth: {
type: 'number',
},
properties: { $ref: '#/definitions/alignmentOptions' },
methods: { $ref: '#/definitions/alignmentOptions' },

Expand Down Expand Up @@ -109,6 +116,7 @@ const METHODS = 'methods';

// specifics set by the eslint config
const defaultOptions = {
printWidth: 100,
[PROPERTIES]: PREFER_INLINE,
[METHODS]: ABOVE,
overrides: {
Expand All @@ -117,9 +125,17 @@ const defaultOptions = {
},
};

let prettier;

function decoratorPositionRule(context) {
if (prettier && prettier.clearConfigCache) {
prettier.clearConfigCache();
}

const userOptions = context.options[0] || {};
const options = normalizeOptions(userOptions);
const filePath = context.getFilename();
const prettierOptions = lineLength(userOptions, filePath);
const options = normalizeOptions(prettierOptions);

return {
'ClassProperty[decorators.length=1]:exit'(node) {
Expand Down Expand Up @@ -162,16 +178,21 @@ function positionDecorator(context, node, options) {
(key === PROPERTIES && node.type === 'ClassProperty') ||
(key === METHODS && node.type === 'MethodDefinition');

let overridesConfig;
if (isMemberRelevant) {
if (position === PREFER_INLINE) {
placeDecoratorsBesideProperty(context, node, {
overridesConfig = {
printWidth: options.printWidth,
overrides: { [PREFER_INLINE]: decorators },
});
};
placeDecoratorsBesideProperty(context, node, overridesConfig);
} else {
// above
placeDecoratorsAboveProperty(context, node, {
overridesConfig = {
printWidth: options.printWidth,
overrides: { [ABOVE]: decorators },
});
};
placeDecoratorsAboveProperty(context, node, overridesConfig);
}
}
});
Expand All @@ -185,25 +206,37 @@ function placeDecoratorsBesideProperty(context, node, options) {
const config = normalizeConfig(decoratorConfig, INTENT.SAME_LINE);
const info = decoratorInfo(node, config);

if (!info.needsTransform) {
continue;
}
const decorator = node.decorators[0];
const token = context.getSourceCode().getTokenAfter(decorator, { includeComments: true });

context.report({
node,
message: `Expected @${info.name} to be inline.`,
const whitespaceStart = decorator.range[1];
const whitespaceEnd = token.range[0];

fix(fixer) {
const decorator = node.decorators[0];
const token = context.getSourceCode().getTokenAfter(decorator, { includeComments: true });
const whitespaceStart = decorator.range[1];
const whitespaceEnd = token.range[0];
const whitespaceLength = whitespaceEnd - whitespaceStart;

// delete the whitespace between the end of the decorator
// and the decorated thing
return fixer.replaceTextRange([whitespaceStart, whitespaceEnd], ' ');
},
});
const totalLineLength = calculateTotalLineLength(context, node, token) + whitespaceLength;
const lessThanOrEqualToPrintWidth = totalLineLength <= Number(options.printWidth);

if (lessThanOrEqualToPrintWidth) {
if (!info.needsTransform) {
continue;
}

context.report({
node,
message: `Expected @${info.name} to be inline.`,

fix(fixer) {
const token = context.getSourceCode().getTokenAfter(decorator, { includeComments: true });
const whitespaceStart = decorator.range[1];
const whitespaceEnd = token.range[0];

// delete the whitespace between the end of the decorator
// and the decorated thing
return fixer.replaceTextRange([whitespaceStart, whitespaceEnd], ' ');
},
});
}
}
}

Expand All @@ -215,36 +248,59 @@ function placeDecoratorsAboveProperty(context, node, options) {
const config = normalizeConfig(decoratorConfig, INTENT.DIFFERENT_LINES);
const info = decoratorInfo(node, config);

if (!info.needsTransform) {
continue;
}
const decorator = node.decorators[0];
const token = context.getSourceCode().getTokenAfter(decorator, { includeComments: true });

context.report({
node,
message: `Expected @${info.name} to be on the line above.`,
const whitespaceStart = decorator.range[1];
const whitespaceEnd = token.range[0] - 1;

fix(fixer) {
const decorator = node.decorators[0];
const indentation = decorator.loc.start.column - 1;
const padding = indentation > 0 ? ' '.repeat(indentation) : '';
const token = context.getSourceCode().getTokenAfter(decorator, { includeComments: true });
const whitespaceLength = whitespaceEnd - whitespaceStart;
// eslint-disable-next-line no-unused-vars
const totalLineLength = calculateTotalLineLength(context, node, token) + whitespaceLength;
const greaterThanOrEqualToPrintWidth = true;

const whitespaceStart = decorator.range[1];
const whitespaceEnd = token.range[0] - 1;
if (greaterThanOrEqualToPrintWidth) {
if (!info.needsTransform) {
continue;
}

// delete the space(s) between the decorator and the
// decorated thing with a newline, matching the
// indentation of the decorator
return fixer.replaceTextRange([whitespaceStart, whitespaceEnd], `\n${padding}`);
},
});
context.report({
node,
message: `Expected @${info.name} to be on the line above.`,

fix(fixer) {
const indentation = decorator.loc.start.column - 1;
const padding = indentation > 0 ? ' '.repeat(indentation) : '';
const token = context.getSourceCode().getTokenAfter(decorator, { includeComments: true });

const whitespaceStart = decorator.range[1];
const whitespaceEnd = token.range[0] - 1;

// delete the space(s) between the decorator and the
// decorated thing with a newline, matching the
// indentation of the decorator
return fixer.replaceTextRange([whitespaceStart, whitespaceEnd], `\n${padding}`);
},
});
}
}
}

// ///////////////////////////////////
// Helpers
// ///////////////////////////////////

function calculateTotalLineLength(context, node, token) {
const decorator = node.decorators[0];
const punctuator = context.getSourceCode().getTokenAfter(token, { includeComments: true });

const [decStart, decEnd] = decorator.range;
const [tokenStart, tokenEnd] = token.range;
const [puncStart, puncEnd] = punctuator.range;

return decEnd - decStart + tokenEnd - tokenStart + puncEnd - puncStart;
}

function normalizeOptions(userOptions) {
const options = Object.assign({}, defaultOptions, userOptions);

Expand All @@ -265,6 +321,28 @@ function configuredDecoratorsInOptions(options) {
return allConfigs.map((config) => config[0]);
}

function lineLength(userOptions, filePath) {
if (!prettier) {
prettier = require('prettier');
}

const eslintPrettierRules = cli.getConfigForFile(filePath).rules['prettier/prettier'] || [];
const eslintPrettierOptions = eslintPrettierRules[1] || {};
const usePrettierrc = !eslintPrettierOptions || eslintPrettierOptions.usePrettierrc !== false;
const prettierRcOptions = usePrettierrc
? prettier.resolveConfig.sync(filePath, {
editorconfig: true,
})
: {};

const prettierOptions = Object.assign({}, prettierRcOptions, eslintPrettierOptions, {
filePath,
});
const r = Object.assign({}, prettierOptions, userOptions);
// console.log(r);
return r;
}

function normalizeConfig(config, intent) {
let name;
let options = {};
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"lint:js": "eslint . --cache",
"start": "yarn run test:watch",
"test": "jest",
"test:debug": "node --inspect node_modules/.bin/jest --watch --runInBand",
"test:debug-watch": "node --inspect node_modules/.bin/jest --runInBand",
"test:coverage": "jest --coverage",
"test:watch": "jest --watchAll",
"update": "node ./scripts/update-rules.js",
Expand Down Expand Up @@ -84,7 +86,9 @@
"dependencies": {
"@ember-data/rfc395-data": "^0.0.4",
"ember-rfc176-data": "^0.3.12",
"snake-case": "^3.0.3"
"snake-case": "^3.0.3",
"prettier": "2.0.5",
"eslint": "7.2.0"
},
"changelog": {
"repo": "NullVoxPopuli/eslint-plugin-decorator-position",
Expand Down
8 changes: 8 additions & 0 deletions scripts/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ set -e

target=$1

echo "About to test scenario: $target"
echo ""
echo "Available scenarios:"
echo " -rules/position-default"
echo " -rules/position-prettier"
echo " ember"
echo ""

name="eslint-plugin-decorator-position"

yarn
Expand Down
10 changes: 10 additions & 0 deletions smoke-tests/-rules/position-prettier/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,15 @@ module.exports = {
plugins: ['prettier'],
rules: {
'decorator-position/decorator-position': ['error'],
'prettier/prettier': [
'error',
{
singleQuote: true,
printWidth: 100,
semi: true,
trailingComma: 'es5',
quoteProps: 'preserve',
},
],
},
};
11 changes: 11 additions & 0 deletions smoke-tests/-rules/position-prettier/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,15 @@ export class Foo {

@foo
async myMethod3() {}

@alias(
'foo.bar.baz.someReallyLongPropertyNameThatIsTooLongToBeInlineOrItBreaksPrettier.Prettier.is.Set.to.100'
)
myProp;

@hasMany('some-very-long-relationship-name-channel-context-chain', {
async: true,
inverse: 'members',
})
memberOf;
}
3 changes: 3 additions & 0 deletions tests/lib/rules/decorator-position.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ ruleTester.run('JS: decorator-position', rule, {
stripIndent`
class Foo {
@foo foo;
@alias('foo.bar.baz.someReallyLongPropertyNameThatIsTooLongToBeInlineOrItBreaksPrettier.Prettier.is.Set.to.100')
myProp;
}
`,
stripIndent`
Expand Down

0 comments on commit e3e621d

Please sign in to comment.