diff --git a/src/rules/memberOrderingRule.ts b/src/rules/memberOrderingRule.ts index 8d558c9b2d8..834dac52d70 100644 --- a/src/rules/memberOrderingRule.ts +++ b/src/rules/memberOrderingRule.ts @@ -18,16 +18,28 @@ import * as ts from "typescript"; import * as Lint from "../index"; -/* start old options */ -const OPTION_VARIABLES_BEFORE_FUNCTIONS = "variables-before-functions"; -const OPTION_STATIC_BEFORE_INSTANCE = "static-before-instance"; -const OPTION_PUBLIC_BEFORE_PRIVATE = "public-before-private"; -/* end old options */ - -/* start new options */ const OPTION_ORDER = "order"; -const PRESET_ORDERS: { [preset: string]: string[] } = { - "fields-first": [ + +enum MemberKind { + publicStaticField, + publicStaticMethod, + protectedStaticField, + protectedStaticMethod, + privateStaticField, + privateStaticMethod, + publicInstanceField, + protectedInstanceField, + privateInstanceField, + publicConstructor, + protectedConstructor, + privateConstructor, + publicInstanceMethod, + protectedInstanceMethod, + privateInstanceMethod, +} + +const PRESETS = new Map([ + ["fields-first", [ "public-static-field", "protected-static-field", "private-static-field", @@ -41,8 +53,8 @@ const PRESET_ORDERS: { [preset: string]: string[] } = { "public-instance-method", "protected-instance-method", "private-instance-method", - ], - "instance-sandwich": [ + ]], + ["instance-sandwich", [ "public-static-field", "protected-static-field", "private-static-field", @@ -56,8 +68,8 @@ const PRESET_ORDERS: { [preset: string]: string[] } = { "public-static-method", "protected-static-method", "private-static-method", - ], - "statics-first": [ + ]], + ["statics-first", [ "public-static-field", "public-static-method", "protected-static-field", @@ -71,9 +83,42 @@ const PRESET_ORDERS: { [preset: string]: string[] } = { "public-instance-method", "protected-instance-method", "private-instance-method", - ], -}; -/* end new options */ + ]], +]); +const PRESET_NAMES = Array.from(PRESETS.keys()); + +const allMemberKindNames = mapDefined(Object.keys(MemberKind), (key) => { + const mk = (MemberKind as any)[key]; + return typeof mk === "number" ? MemberKind[mk].replace(/[A-Z]/g, (cap) => "-" + cap.toLowerCase()) : undefined; +}); + +function namesMarkdown(names: string[]): string { + return names.map((name) => "* `" + name + "`").join("\n "); +} + +const optionsDescription = Lint.Utils.dedent` + One argument, which is an object, must be provided. It should contain an \`order\` property. + The \`order\` property should have a value of one of the following strings: + + ${namesMarkdown(PRESET_NAMES)} + + Alternatively, the value for \`order\` maybe be an array consisting of the following strings: + + ${namesMarkdown(allMemberKindNames)} + + You can also omit the access modifier to refer to "public-", "protected-", and "private-" all at once; for example, "static-field". + + You can also make your own categories by using an object instead of a string: + + { + "name": "static non-private", + "kinds": [ + "public-static-field", + "protected-static-field", + "public-static-method", + "protected-static-method" + ] + }`; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -81,43 +126,19 @@ export class Rule extends Lint.Rules.AbstractRule { ruleName: "member-ordering", description: "Enforces member ordering.", rationale: "A consistent ordering for class members can make classes easier to read, navigate, and edit.", - optionsDescription: Lint.Utils.dedent` - One argument, which is an object, must be provided. It should contain an \`order\` property. - The \`order\` property should have a value of one of the following strings: - - * \`fields-first\` - * \`statics-first\` - * \`instance-sandwich\` - - Alternatively, the value for \`order\` maybe be an array consisting of the following strings: - - * \`public-static-field\` - * \`protected-static-field\` - * \`private-static-field\` - * \`public-instance-field\` - * \`protected-instance-field\` - * \`private-instance-field\` - * \`constructor\` - * \`public-static-method\` - * \`protected-static-method\` - * \`private-static-method\` - * \`public-instance-method\` - * \`protected-instance-method\` - * \`private-instance-method\` - - This is useful if one of the preset orders does not meet your needs.`, + optionsDescription, options: { type: "object", properties: { order: { oneOf: [{ type: "string", - enum: ["fields-first", "statics-first", "instance-sandwich"], + enum: PRESET_NAMES, }, { type: "array", items: { type: "string", - enum: PRESET_ORDERS["statics-first"], + enum: allMemberKindNames, }, maxLength: 13, }], @@ -125,7 +146,35 @@ export class Rule extends Lint.Rules.AbstractRule { }, additionalProperties: false, }, - optionExamples: ['[true, { "order": "fields-first" }]'], + optionExamples: [ + '[true, { "order": "fields-first" }]', + Lint.Utils.dedent` + [true, { + "order": [ + "static-field", + "instance-field", + "constructor", + "public-instance-method", + "protected-instance-method", + "private-instance-method" + ] + }]`, + Lint.Utils.dedent` + [true, { + "order": [ + { + "name": "static non-private", + "kinds": [ + "public-static-field", + "protected-static-field", + "public-static-method", + "protected-static-method" + ] + }, + "constructor" + ] + }]`, + ], type: "typescript", typescriptOnly: true, }; @@ -135,262 +184,253 @@ export class Rule extends Lint.Rules.AbstractRule { } } -/* start code supporting old options (i.e. "public-before-private") */ -interface IModifiers { - isMethod: boolean; - isPrivate: boolean; - isInstance: boolean; -} - -function getModifiers(isMethod: boolean, modifiers?: ts.ModifiersArray): IModifiers { - return { - isInstance: !Lint.hasModifier(modifiers, ts.SyntaxKind.StaticKeyword), - isMethod, - isPrivate: Lint.hasModifier(modifiers, ts.SyntaxKind.PrivateKeyword), - }; -} - -function toString(modifiers: IModifiers): string { - return [ - modifiers.isPrivate ? "private" : "public", - modifiers.isInstance ? "instance" : "static", - "member", - modifiers.isMethod ? "function" : "variable", - ].join(" "); -} -/* end old code */ - -/* start new code */ -enum AccessLevel { - PRIVATE, - PROTECTED, - PUBLIC, -} - -enum Membership { - INSTANCE, - STATIC, -} - -enum Kind { - FIELD, - METHOD, -} - -interface INodeAndModifiers { - accessLevel: AccessLevel; - isConstructor: boolean; - kind: Kind; - membership: Membership; - node: ts.Node; -} - -function getNodeAndModifiers(node: ts.Node, isMethod: boolean, isConstructor = false): INodeAndModifiers { - const { modifiers } = node; - const accessLevel = Lint.hasModifier(modifiers, ts.SyntaxKind.PrivateKeyword) ? AccessLevel.PRIVATE - : Lint.hasModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) ? AccessLevel.PROTECTED - : AccessLevel.PUBLIC; - const kind = isMethod ? Kind.METHOD : Kind.FIELD; - const membership = Lint.hasModifier(modifiers, ts.SyntaxKind.StaticKeyword) ? Membership.STATIC : Membership.INSTANCE; - return { - accessLevel, - isConstructor, - kind, - membership, - node, - }; -} +export class MemberOrderingWalker extends Lint.RuleWalker { + private readonly order: MemberCategory[]; -function getNodeOption({accessLevel, isConstructor, kind, membership}: INodeAndModifiers) { - if (isConstructor) { - return "constructor"; + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { + super(sourceFile, options); + this.order = getOrder(this.getOptions()); } - return [ - AccessLevel[accessLevel].toLowerCase(), - Membership[membership].toLowerCase(), - Kind[kind].toLowerCase(), - ].join("-"); -} -/* end new code */ - -export class MemberOrderingWalker extends Lint.RuleWalker { - private previousMember: IModifiers; - private memberStack: INodeAndModifiers[][] = []; - private hasOrderOption = this.getHasOrderOption(); - public visitClassDeclaration(node: ts.ClassDeclaration) { - this.resetPreviousModifiers(); - - this.newMemberList(); + this.visitMembers(node.members); super.visitClassDeclaration(node); - this.checkMemberOrder(); } public visitClassExpression(node: ts.ClassExpression) { - this.resetPreviousModifiers(); - - this.newMemberList(); + this.visitMembers(node.members); super.visitClassExpression(node); - this.checkMemberOrder(); } public visitInterfaceDeclaration(node: ts.InterfaceDeclaration) { - this.resetPreviousModifiers(); - - this.newMemberList(); + this.visitMembers(node.members); super.visitInterfaceDeclaration(node); - this.checkMemberOrder(); } - public visitMethodDeclaration(node: ts.MethodDeclaration) { - this.checkModifiersAndSetPrevious(node, getModifiers(true, node.modifiers)); - this.pushMember(getNodeAndModifiers(node, true)); - super.visitMethodDeclaration(node); + public visitTypeLiteral(node: ts.TypeLiteralNode) { + this.visitMembers(node.members); + super.visitTypeLiteral(node); } - public visitMethodSignature(node: ts.SignatureDeclaration) { - this.checkModifiersAndSetPrevious(node, getModifiers(true, node.modifiers)); - this.pushMember(getNodeAndModifiers(node, true)); - super.visitMethodSignature(node); + private visitMembers(members: Member[]) { + let prevRank = -1; + for (const member of members) { + const rank = this.memberRank(member); + if (rank === -1) { + // no explicit ordering for this kind of node specified, so continue + continue; + } + + if (rank < prevRank) { + const nodeType = this.rankName(rank); + const prevNodeType = this.rankName(prevRank); + const lowerRank = this.findLowerRank(members, rank); + const locationHint = lowerRank !== -1 + ? `after ${this.rankName(lowerRank)}s` + : "at the beginning of the class/interface"; + const errorLine1 = `Declaration of ${nodeType} not allowed after declaration of ${prevNodeType}. ` + + `Instead, this should come ${locationHint}.`; + this.addFailureAtNode(member, errorLine1); + } else { + // keep track of last good node + prevRank = rank; + } + } } - public visitConstructorDeclaration(node: ts.ConstructorDeclaration) { - this.checkModifiersAndSetPrevious(node, getModifiers(true, node.modifiers)); - this.pushMember(getNodeAndModifiers(node, true, true)); - super.visitConstructorDeclaration(node); + /** Finds the highest existing rank lower than `targetRank`. */ + private findLowerRank(members: Member[], targetRank: Rank): Rank | -1 { + let max: Rank | -1 = -1; + for (const member of members) { + const rank = this.memberRank(member); + if (rank !== -1 && rank < targetRank) { + max = Math.max(max, rank); + } + } + return max; } - public visitPropertyDeclaration(node: ts.PropertyDeclaration) { - const { initializer } = node; - const isFunction = initializer != null - && (initializer.kind === ts.SyntaxKind.ArrowFunction || initializer.kind === ts.SyntaxKind.FunctionExpression); - this.checkModifiersAndSetPrevious(node, getModifiers(isFunction, node.modifiers)); - this.pushMember(getNodeAndModifiers(node, isFunction)); - super.visitPropertyDeclaration(node); + private memberRank(member: Member): Rank | -1 { + const optionName = getMemberKind(member); + if (optionName === undefined) { + return -1; + } + return this.order.findIndex((category) => category.has(optionName)); } - public visitPropertySignature(node: ts.PropertyDeclaration) { - this.checkModifiersAndSetPrevious(node, getModifiers(false, node.modifiers)); - this.pushMember(getNodeAndModifiers(node, false)); - super.visitPropertySignature(node); + private rankName(rank: Rank): string { + return this.order[rank].name; } +} + +function memberKindForConstructor(access: Access): MemberKind { + return (MemberKind as any)[access + "Constructor"]; +} + +function memberKindForMethodOrField(access: Access, membership: "Static" | "Instance", kind: "Method" | "Field"): MemberKind { + return (MemberKind as any)[access + membership + kind]; +} + +const allAccess: Access[] = ["public", "protected", "private"]; - public visitTypeLiteral(_node: ts.TypeLiteralNode) { - // don't call super from here -- we want to skip the property declarations in type literals +function memberKindFromName(name: string): MemberKind[] { + const kind = (MemberKind as any)[Lint.Utils.camelize(name)]; + return typeof kind === "number" ? [kind as MemberKind] : allAccess.map(addModifier); + + function addModifier(modifier: string) { + const modifiedKind = (MemberKind as any)[Lint.Utils.camelize(modifier + "-" + name)]; + if (typeof modifiedKind !== "number") { + throw new Error(`Bad member kind: ${name}`); + } + return modifiedKind; } +} + +function getMemberKind(member: Member): MemberKind | undefined { + const accessLevel = hasModifier(ts.SyntaxKind.PrivateKeyword) ? "private" + : hasModifier(ts.SyntaxKind.ProtectedKeyword) ? "protected" + : "public"; + + switch (member.kind) { + case ts.SyntaxKind.Constructor: + case ts.SyntaxKind.ConstructSignature: + return memberKindForConstructor(accessLevel); + + case ts.SyntaxKind.PropertyDeclaration: + case ts.SyntaxKind.PropertySignature: + return methodOrField(isFunctionLiteral((member as ts.PropertyDeclaration).initializer)); - public visitObjectLiteralExpression(_node: ts.ObjectLiteralExpression) { - // again, don't call super here - object literals can have methods, - // and we don't wan't to check these + case ts.SyntaxKind.MethodDeclaration: + case ts.SyntaxKind.MethodSignature: + return methodOrField(true); + + default: + return undefined; } - /* start old code */ - private resetPreviousModifiers() { - this.previousMember = { - isInstance: false, - isMethod: false, - isPrivate: false, - }; + function methodOrField(isMethod: boolean) { + const membership = hasModifier(ts.SyntaxKind.StaticKeyword) ? "Static" : "Instance"; + return memberKindForMethodOrField(accessLevel, membership, isMethod ? "Method" : "Field"); } - private checkModifiersAndSetPrevious(node: ts.Node, currentMember: IModifiers) { - if (!this.canAppearAfter(this.previousMember, currentMember)) { - this.addFailureAtNode(node, - `Declaration of ${toString(currentMember)} not allowed to appear after declaration of ${toString(this.previousMember)}`); - } - this.previousMember = currentMember; + function hasModifier(kind: ts.SyntaxKind) { + return Lint.hasModifier(member.modifiers, kind); } +} - private canAppearAfter(previousMember: IModifiers, currentMember: IModifiers) { - if (previousMember == null || currentMember == null) { - return true; - } +type MemberCategoryJson = { name: string, kinds: string[] } | string; +class MemberCategory { + constructor(readonly name: string, private readonly kinds: Set) {} + public has(kind: MemberKind) { return this.kinds.has(kind); } +} - if (this.hasOption(OPTION_VARIABLES_BEFORE_FUNCTIONS) && previousMember.isMethod !== currentMember.isMethod) { - return Number(previousMember.isMethod) < Number(currentMember.isMethod); - } +type Member = ts.TypeElement | ts.ClassElement; +type Rank = number; - if (this.hasOption(OPTION_STATIC_BEFORE_INSTANCE) && previousMember.isInstance !== currentMember.isInstance) { - return Number(previousMember.isInstance) < Number(currentMember.isInstance); - } +type Access = "public" | "protected" | "private"; - if (this.hasOption(OPTION_PUBLIC_BEFORE_PRIVATE) && previousMember.isPrivate !== currentMember.isPrivate) { - return Number(previousMember.isPrivate) < Number(currentMember.isPrivate); - } +function getOrder(options: any[]): MemberCategory[] { + return getOrderJson(options).map((cat) => typeof cat === "string" + ? new MemberCategory(cat.replace(/-/g, " "), new Set(memberKindFromName(cat))) + : new MemberCategory(cat.name, new Set(flatMap(cat.kinds, memberKindFromName)))); +} +function getOrderJson(allOptions: any[]): MemberCategoryJson[] { + if (allOptions == null || allOptions.length === 0 || allOptions[0] == null) { + throw new Error("Got empty options"); + } - return true; + const firstOption = allOptions[0]; + if (typeof firstOption !== "object") { + // Undocumented direct string option. Deprecate eventually. + return convertFromOldStyleOptions(allOptions); // presume it to be string[] } - /* end old code */ - /* start new code */ - private newMemberList() { - if (this.hasOrderOption) { - this.memberStack.push([]); - } + return categoryFromOption(firstOption[OPTION_ORDER]); +} +function categoryFromOption(orderOption: {}): MemberCategoryJson[] { + if (Array.isArray(orderOption)) { + return orderOption; } - private pushMember(node: INodeAndModifiers) { - if (this.hasOrderOption) { - this.memberStack[this.memberStack.length - 1].push(node); - } + const preset = PRESETS.get(orderOption as string); + if (!preset) { + throw new Error(`Bad order: ${JSON.stringify(orderOption)}`); + } + return preset; +} + +/** + * Convert from undocumented old-style options. + * This is designed to mimic the old behavior and should be removed eventually. + */ +function convertFromOldStyleOptions(options: string[]): MemberCategoryJson[] { + let categories: NameAndKinds[] = [{ name: "member", kinds: allMemberKindNames }]; + if (hasOption("variables-before-functions")) { + categories = splitOldStyleOptions(categories, (kind) => kind.includes("field"), "field", "method"); } + if (hasOption("static-before-instance")) { + categories = splitOldStyleOptions(categories, (kind) => kind.includes("static"), "static", "instance"); + } + if (hasOption("public-before-private")) { + // 'protected' is considered public + categories = splitOldStyleOptions(categories, (kind) => !kind.includes("private"), "public", "private"); + } + return categories; - private checkMemberOrder() { - if (this.hasOrderOption) { - const memberList = this.memberStack.pop(); - const order = this.getOrder(); - if (memberList !== undefined && order !== null) { - const memberRank = memberList.map((n) => order.indexOf(getNodeOption(n))); - - let prevRank = -1; - memberRank.forEach((rank, i) => { - // no explicit ordering for this kind of node specified, so continue - if (rank === -1) { return; } - - // node should have come before last node, so add a failure - if (rank < prevRank) { - // generate a nice and clear error message - const node = memberList[i].node; - const nodeType = order[rank].split("-").join(" "); - const prevNodeType = order[prevRank].split("-").join(" "); - - const lowerRanks = memberRank.filter((r) => r < rank && r !== -1).sort(); - const locationHint = lowerRanks.length > 0 - ? `after ${order[lowerRanks[lowerRanks.length - 1]].split("-").join(" ")}s` - : "at the beginning of the class/interface"; - - const errorLine1 = `Declaration of ${nodeType} not allowed after declaration of ${prevNodeType}. ` + - `Instead, this should come ${locationHint}.`; - this.addFailureAtNode(node, errorLine1); - } else { - // keep track of last good node - prevRank = rank; - } - }); + function hasOption(x: string): boolean { + return options.indexOf(x) !== -1; + } +} +interface NameAndKinds { name: string; kinds: string[]; } +function splitOldStyleOptions(categories: NameAndKinds[], filter: (name: string) => boolean, a: string, b: string): NameAndKinds[] { + const newCategories: NameAndKinds[] = []; + for (const cat of categories) { + const yes = []; const no = []; + for (const kind of cat.kinds) { + if (filter(kind)) { + yes.push(kind); + } else { + no.push(kind); } } + const augmentName = (s: string) => { + if (a === "field") { + // Replace "member" with "field"/"method" instead of augmenting. + return s; + } + return `${s} ${cat.name}`; + }; + newCategories.push({ name: augmentName(a), kinds: yes }); + newCategories.push({ name: augmentName(b), kinds: no }); } + return newCategories; +} - private getHasOrderOption() { - const allOptions = this.getOptions(); - if (allOptions == null || allOptions.length === 0) { +function isFunctionLiteral(node: ts.Node | undefined) { + switch (node && node.kind) { + case ts.SyntaxKind.ArrowFunction: + case ts.SyntaxKind.FunctionExpression: + return true; + default: return false; - } - - const firstOption = allOptions[0]; - return firstOption != null && typeof firstOption === "object" && firstOption[OPTION_ORDER] != null; } +} - // assumes this.hasOrderOption() === true - private getOrder(): string[] | null { - const orderOption = this.getOptions()[0][OPTION_ORDER]; - if (Array.isArray(orderOption)) { - return orderOption; - } else if (typeof orderOption === "string") { - return PRESET_ORDERS[orderOption] || PRESET_ORDERS["default"]; +function mapDefined(inputs: T[], getOutput: (input: T) => U | undefined): U[] { + const out = []; + for (const input of inputs) { + const output = getOutput(input); + if (output !== undefined) { + out.push(output); } - return null; } - /* end new code */ + return out; +} + +function flatMap(inputs: T[], getOutputs: (input: T) => U[]): U[] { + const out = []; + for (const input of inputs) { + out.push(...getOutputs(input)); + } + return out; } diff --git a/test/rules/member-ordering/custom-categories/test.ts.lint b/test/rules/member-ordering/custom-categories/test.ts.lint new file mode 100644 index 00000000000..6df80c3273e --- /dev/null +++ b/test/rules/member-ordering/custom-categories/test.ts.lint @@ -0,0 +1,10 @@ +class C { + static foo() {} + protected static bar = 0; + static baz(); + + constructor(); + + static bang(); + ~~~~~~~~~~~~~~ [Declaration of static non-private not allowed after declaration of constructor. Instead, this should come at the beginning of the class/interface.] +} diff --git a/test/rules/member-ordering/custom-categories/tslint.json b/test/rules/member-ordering/custom-categories/tslint.json new file mode 100644 index 00000000000..ddacc962238 --- /dev/null +++ b/test/rules/member-ordering/custom-categories/tslint.json @@ -0,0 +1,18 @@ +{ + "rules": { + "member-ordering": [true, { + "order": [ + { + "name": "static non-private", + "kinds": [ + "public-static-field", + "protected-static-field", + "public-static-method", + "protected-static-method" + ] + }, + "constructor" + ] + }] + } +} diff --git a/test/rules/member-ordering/order/custom/test.ts.lint b/test/rules/member-ordering/custom/test.ts.lint similarity index 100% rename from test/rules/member-ordering/order/custom/test.ts.lint rename to test/rules/member-ordering/custom/test.ts.lint diff --git a/test/rules/member-ordering/order/custom/tslint.json b/test/rules/member-ordering/custom/tslint.json similarity index 100% rename from test/rules/member-ordering/order/custom/tslint.json rename to test/rules/member-ordering/custom/tslint.json diff --git a/test/rules/member-ordering/order/fields-first/test.ts.lint b/test/rules/member-ordering/fields-first/test.ts.lint similarity index 100% rename from test/rules/member-ordering/order/fields-first/test.ts.lint rename to test/rules/member-ordering/fields-first/test.ts.lint diff --git a/test/rules/member-ordering/order/fields-first/tslint.json b/test/rules/member-ordering/fields-first/tslint.json similarity index 100% rename from test/rules/member-ordering/order/fields-first/tslint.json rename to test/rules/member-ordering/fields-first/tslint.json diff --git a/test/rules/member-ordering/order/instance-sandwich/test.ts.lint b/test/rules/member-ordering/instance-sandwich/test.ts.lint similarity index 100% rename from test/rules/member-ordering/order/instance-sandwich/test.ts.lint rename to test/rules/member-ordering/instance-sandwich/test.ts.lint diff --git a/test/rules/member-ordering/order/instance-sandwich/tslint.json b/test/rules/member-ordering/instance-sandwich/tslint.json similarity index 100% rename from test/rules/member-ordering/order/instance-sandwich/tslint.json rename to test/rules/member-ordering/instance-sandwich/tslint.json diff --git a/test/rules/member-ordering/mix-old-options/test.ts.lint b/test/rules/member-ordering/mix-old-options/test.ts.lint new file mode 100644 index 00000000000..109ef9699e3 --- /dev/null +++ b/test/rules/member-ordering/mix-old-options/test.ts.lint @@ -0,0 +1,16 @@ +class Foo { + public static x: number; + private static y: number; + public x: number; + private y: number; + + public static m() {} + private static n() {} + + constructor() {} + public m() {} + private n() {} + + z: number; + ~~~~~~~~~~ [Declaration of public instance field not allowed after declaration of private instance method. Instead, this should come after private static fields.] +} diff --git a/test/rules/member-ordering/mix-old-options/tslint.json b/test/rules/member-ordering/mix-old-options/tslint.json new file mode 100644 index 00000000000..659333c1fbe --- /dev/null +++ b/test/rules/member-ordering/mix-old-options/tslint.json @@ -0,0 +1,9 @@ +{ + "rules": { + "member-ordering": [true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ] + } +} diff --git a/test/rules/member-ordering/omit-access-modifier/test.ts.lint b/test/rules/member-ordering/omit-access-modifier/test.ts.lint new file mode 100644 index 00000000000..fdfbf683047 --- /dev/null +++ b/test/rules/member-ordering/omit-access-modifier/test.ts.lint @@ -0,0 +1,12 @@ +class C { + private static x = 0; + static y = 1; + + x = 0; + private y = 1; + + constructor() {} + + static z = 2; + ~~~~~~~~~~~~~ [Declaration of static field not allowed after declaration of constructor. Instead, this should come at the beginning of the class/interface.] +} diff --git a/test/rules/member-ordering/omit-access-modifier/tslint.json b/test/rules/member-ordering/omit-access-modifier/tslint.json new file mode 100644 index 00000000000..c88314eb612 --- /dev/null +++ b/test/rules/member-ordering/omit-access-modifier/tslint.json @@ -0,0 +1,9 @@ +{ + "rules": { + "member-ordering": [true, { "order": [ + "static-field", + "instance-field", + "constructor" + ]}] + } +} diff --git a/test/rules/member-ordering/private/test.ts.lint b/test/rules/member-ordering/private/test.ts.lint deleted file mode 100644 index 24f637796f7..00000000000 --- a/test/rules/member-ordering/private/test.ts.lint +++ /dev/null @@ -1,10 +0,0 @@ -class Foo { - private x: number; - private bar(): any { - var bla: { a: string } = {a: '1'}; - } - y: number; - ~~~~~~~~~~ [0] -} - -[0]: Declaration of public instance member variable not allowed to appear after declaration of private instance member function diff --git a/test/rules/member-ordering/public-before-private/test.ts.lint b/test/rules/member-ordering/public-before-private/test.ts.lint new file mode 100644 index 00000000000..c26be55d8ac --- /dev/null +++ b/test/rules/member-ordering/public-before-private/test.ts.lint @@ -0,0 +1,10 @@ +class Foo { + private x: number; + private bar(): any { + var bla: { a: string } = {a: '1'}; + } + y: number; + ~~~~~~~~~~ [0] +} + +[0]: Declaration of public member not allowed after declaration of private member. Instead, this should come at the beginning of the class/interface. diff --git a/test/rules/member-ordering/private/tslint.json b/test/rules/member-ordering/public-before-private/tslint.json similarity index 100% rename from test/rules/member-ordering/private/tslint.json rename to test/rules/member-ordering/public-before-private/tslint.json diff --git a/test/rules/member-ordering/static-before-instance/test.ts.lint b/test/rules/member-ordering/static-before-instance/test.ts.lint new file mode 100644 index 00000000000..532440d9837 --- /dev/null +++ b/test/rules/member-ordering/static-before-instance/test.ts.lint @@ -0,0 +1,10 @@ +class Foo { + x: number; + static y: number; + ~~~~~~~~~~~~~~~~~ [0] + constructor() { + // nothing to do + } +} + +[0]: Declaration of static member not allowed after declaration of instance member. Instead, this should come at the beginning of the class/interface. diff --git a/test/rules/member-ordering/static/tslint.json b/test/rules/member-ordering/static-before-instance/tslint.json similarity index 100% rename from test/rules/member-ordering/static/tslint.json rename to test/rules/member-ordering/static-before-instance/tslint.json diff --git a/test/rules/member-ordering/static/test.ts.lint b/test/rules/member-ordering/static/test.ts.lint deleted file mode 100644 index b5396b81a18..00000000000 --- a/test/rules/member-ordering/static/test.ts.lint +++ /dev/null @@ -1,10 +0,0 @@ -class Foo { - x: number; - static y: number; - ~~~~~~~~~~~~~~~~~ [0] - constructor() { - // nothing to do - } -} - -[0]: Declaration of public static member variable not allowed to appear after declaration of public instance member variable diff --git a/test/rules/member-ordering/order/statics-first/test.ts.lint b/test/rules/member-ordering/statics-first/test.ts.lint similarity index 100% rename from test/rules/member-ordering/order/statics-first/test.ts.lint rename to test/rules/member-ordering/statics-first/test.ts.lint diff --git a/test/rules/member-ordering/order/statics-first/tslint.json b/test/rules/member-ordering/statics-first/tslint.json similarity index 100% rename from test/rules/member-ordering/order/statics-first/tslint.json rename to test/rules/member-ordering/statics-first/tslint.json diff --git a/test/rules/member-ordering/method/test.ts.lint b/test/rules/member-ordering/variables-before-functions/test.ts.lint similarity index 66% rename from test/rules/member-ordering/method/test.ts.lint rename to test/rules/member-ordering/variables-before-functions/test.ts.lint index 1e552a3cbef..e657b567d72 100644 --- a/test/rules/member-ordering/method/test.ts.lint +++ b/test/rules/member-ordering/variables-before-functions/test.ts.lint @@ -52,5 +52,20 @@ class Constructor2 { ~~~~~~~~~~~~~~~~~ [0] } -[0]: Declaration of public instance member variable not allowed to appear after declaration of public instance member function +// Works for type literal, just like interface +type T = { + x(): void; + y: number; + ~~~~~~~~~~ [0] +} + +// Works for class inside object literal +const o = { + foo: class C { + x(): void; + y: number; + ~~~~~~~~~~ [0] + } +} +[0]: Declaration of field not allowed after declaration of method. Instead, this should come at the beginning of the class/interface. diff --git a/test/rules/member-ordering/method/tslint.json b/test/rules/member-ordering/variables-before-functions/tslint.json similarity index 100% rename from test/rules/member-ordering/method/tslint.json rename to test/rules/member-ordering/variables-before-functions/tslint.json