diff --git a/README.md b/README.md index 360e8e4..241bd36 100644 --- a/README.md +++ b/README.md @@ -1845,7 +1845,7 @@ These rules are purely matters of style and are quite subjective. "no-whitespace-before-property": true ``` -* [object-curly-spacing](http://eslint.org/docs/rules/object-curly-spacing) => object-curly-spacing (tslint-eslint-rules) [TODO]() +* [object-curly-spacing](http://eslint.org/docs/rules/object-curly-spacing) => object-curly-spacing (tslint-eslint-rules) * Description: require or disallow padding inside curly braces * Usage diff --git a/src/rules/objectCurlySpacingRule.ts b/src/rules/objectCurlySpacingRule.ts new file mode 100644 index 0000000..d84ccf1 --- /dev/null +++ b/src/rules/objectCurlySpacingRule.ts @@ -0,0 +1,71 @@ +import * as ts from 'typescript'; +import * as Lint from 'tslint/lib/lint'; + +const OPTION_ALWAYS = 'always'; + +export class Rule extends Lint.Rules.AbstractRule { + public static FAILURE_STRING = { + always: { + start: `A space is required after '{'`, + end: `A space is required before '}'` + }, + never: { + start: `There should be no space after '{'`, + end: `There should be no space before '}'` + } + }; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const walker = new BlockSpacingWalker(sourceFile, this.getOptions()); + return this.applyWithWalker(walker); + } +} + +class BlockSpacingWalker extends Lint.RuleWalker { + + private always: boolean; + + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { + super(sourceFile, options); + this.always = this.hasOption(OPTION_ALWAYS) || (this.getOptions() && this.getOptions().length === 0); + } + + protected visitNode(node: ts.Node): void { + const bracedKind = [ + ts.SyntaxKind.ObjectLiteralExpression, + ts.SyntaxKind.ObjectBindingPattern, + ts.SyntaxKind.NamedImports, + ts.SyntaxKind.NamedExports + ]; + if (bracedKind.indexOf(node.kind) > -1) { + this.checkSpacingInsideBraces(node); + } + super.visitNode(node); + } + + private checkSpacingInsideBraces(node: ts.Node): void { + const text = node.getText(); + if (text.indexOf('\n') !== -1 || text === '{}') { + // Rule does not apply when the braces span multiple lines + return; + } + const leadingSpace = text.match(/^\{(\s{0,2})/)[1].length; + const trailingSpace = text.match(/(\s{0,2})}$/)[1].length; + if (this.always) { + if (leadingSpace === 0) { + this.addFailure(this.createFailure(node.getStart(), 1, Rule.FAILURE_STRING.always.start)); + } + if (trailingSpace === 0) { + this.addFailure(this.createFailure(node.getEnd() - 1, 1, Rule.FAILURE_STRING.always.end)); + } + } else { + if (leadingSpace > 0) { + this.addFailure(this.createFailure(node.getStart(), 1, Rule.FAILURE_STRING.never.start)); + } + if (trailingSpace > 0) { + this.addFailure(this.createFailure(node.getEnd() - 1, 1, Rule.FAILURE_STRING.never.end)); + } + } + } + +} diff --git a/src/test/rules/objectCurlySpacingRuleTests.ts b/src/test/rules/objectCurlySpacingRuleTests.ts new file mode 100644 index 0000000..cb358d4 --- /dev/null +++ b/src/test/rules/objectCurlySpacingRuleTests.ts @@ -0,0 +1,60 @@ +/// +import { makeTest } from './helper'; + +const rule = 'object-curly-spacing'; +const scripts = { + always: { + valid: [ + `const obj = { foo: 'bar' };`, + `const obj = { foo: { zoo: 'bar' } };`, + `const { x, y } = y;`, + `import { foo } from 'bar';`, + `export { foo };` + ], + invalid: [ + `const obj = {foo: 'bar'};`, + `const obj = {foo: { zoo: 'bar' } };`, + `const {x, y} = y;`, + `import {foo } from 'bar';`, + `export { foo};` + ] + }, + never: { + valid: [ + `const obj = {foo: 'bar'};`, + `const obj = {foo: {zoo: 'bar'}};`, + `const {x, y} = y;`, + `import {foo} from 'bar';`, + `export {foo};` + ], + invalid: [ + `const obj = { foo: 'bar' };`, + `const obj = { foo: { zoo: 'bar' } };`, + `const { x, y } = y;`, + `import {foo } from 'bar';`, + `export { foo};` + ] + } +}; + +describe(rule, function test() { + + const alwaysConfig = { rules: { 'object-curly-spacing': [true, 'always'] } }; + const neverConfig = { rules: { 'object-curly-spacing': [true, 'never'] } }; + + it('should pass when "always" and there are spaces inside brackets', function testVariables() { + makeTest(rule, scripts.always.valid, true, alwaysConfig); + }); + + it('should fail when "always" and there are not spaces inside brackets', function testVariables() { + makeTest(rule, scripts.always.invalid, false, alwaysConfig); + }); + + it('should pass when "never" and there are not spaces inside brackets', function testVariables() { + makeTest(rule, scripts.never.valid, true, neverConfig); + }); + + it('should fail when "never" and there are spaces inside brackets', function testVariables() { + makeTest(rule, scripts.never.invalid, false, neverConfig); + }); +});