Add importDefaultBasename rule
MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -100,6 +100,7 @@ type LintDiagnosticCategory =
| 'lint/emptyBlocks'
| 'lint/emptyMatches'
| 'lint/getterReturn'
+ | 'lint/importDefaultBasename'
| 'lint/inconsiderateLanguage'
| 'lint/jsxKey'
| 'lint/jsxNoCommentText'

MODIFY
packages/@romejs/diagnostics/descriptions.ts
packages/@romejs/diagnostics/descriptions.ts
@@ -291,6 +291,21 @@ export const descriptions = createMessages({
},
// @romejs/js-compiler
LINT: {
+ IMPORT_DEFAULT_BASENAME: (prev: string, basename: string) => ({
+ category: 'lint/importDefaultBasename',
+ message: markup`When importing the default, use the basename <emphasis>${basename}</emphasis>`,
+ advice: [
+ {
+ type: 'log',
+ category: 'info',
+ text: 'If you really meant this then use this instead',
+ },
+ {
+ type: 'code',
+ code: markup`import {default as ${prev}}`,
+ },
+ ],
+ }),
NO_COMMA_OPERATOR: {
category: 'lint/noCommaOperator',
message: 'Avoid usage of the comma operator. It can lead to easy mistakes and ambiguous code.',

MODIFY
packages/@romejs/js-compiler/lint/rules/index.ts
packages/@romejs/js-compiler/lint/rules/index.ts
@@ -14,6 +14,7 @@ import duplicateImportSource from './regular/duplicateImportSource';
import emptyBlocks from './regular/emptyBlocks';
import emptyMatches from './regular/emptyMatches';
import getterReturn from './regular/getterReturn';
+import importDefaultBasename from './regular/importDefaultBasename';
import inconsiderateLanguage from './regular/inconsiderateLanguage';
import jsxKey from './react/jsxKey';
import jsxNoCommentText from './react/jsxNoCommentText';
@@ -69,6 +70,7 @@ export const lintTransforms = [
emptyBlocks,
emptyMatches,
getterReturn,
+ importDefaultBasename,
inconsiderateLanguage,
jsxKey,
jsxNoCommentText,

MODIFY
packages/@romejs/js-compiler/lint/rules/regular/camelCase.ts
packages/@romejs/js-compiler/lint/rules/regular/camelCase.ts
@@ -11,15 +11,31 @@ import {Binding} from '@romejs/js-compiler/scope/bindings';
import {descriptions} from '@romejs/diagnostics';
import {
isIdentifierish,
+ isValidIdentifierName,
isVariableIdentifier,
renameBindings,
} from '@romejs/js-ast-utils';
+function normalizeCamelCase(name: string): undefined | string {
+ if (!isValidIdentifierName(name)) {
+ return undefined;
+ }
+
+ if (name === '') {
+ return undefined;
+ }
+
+ return name;
+}
+
// Allow prefixed underscores
-function toVariableCamelCase(name: string): string {
+export function toVariableCamelCase(
+ name: string,
+ forceCapitalize?: boolean,
+): undefined | string {
// Allow shouty constants
if (name.toUpperCase() === name) {
- return name;
+ return normalizeCamelCase(name);
}
let prefix = '';
@@ -41,7 +57,8 @@ function toVariableCamelCase(name: string): string {
slicedName = slicedName.slice(0, -suffix.length);
}
- return prefix + toCamelCase(slicedName) + suffix;
+ const camelName = prefix + toCamelCase(slicedName, forceCapitalize) + suffix;
+ return normalizeCamelCase(camelName);
}
export default {
@@ -55,7 +72,7 @@ export default {
for (const [name, binding] of scope.getOwnBindings()) {
const camelName = toVariableCamelCase(name);
- if (camelName !== name) {
+ if (camelName !== undefined && camelName !== name) {
const {suppressed} = context.addNodeDiagnostic(
binding.node,
descriptions.LINT.VARIABLE_CAMEL_CASE(name, camelName),
@@ -76,7 +93,7 @@ export default {
if (isIdentifierish(node) && !isVariableIdentifier(node)) {
const {name} = node;
const camelName = toVariableCamelCase(name);
- if (camelName !== name) {
+ if (camelName !== undefined && camelName !== name) {
return context.addFixableDiagnostic(
{
old: node,

MODIFY
packages/@romejs/js-compiler/lint/rules/regular/defaultExportSameBasename.ts
packages/@romejs/js-compiler/lint/rules/regular/defaultExportSameBasename.ts
@@ -12,11 +12,11 @@ import {
FunctionDeclaration,
} from '@romejs/js-ast';
import {Path} from '@romejs/js-compiler';
-import {toCamelCase} from '@romejs/string-utils';
import {UnknownFilePath} from '@romejs/path';
import {renameBindings} from '@romejs/js-ast-utils';
import {TransformExitResult} from '@romejs/js-compiler/types';
import {descriptions} from '@romejs/diagnostics';
+import {toVariableCamelCase} from './camelCase';
function isValidDeclaration(
node: AnyNode,
@@ -24,7 +24,10 @@ function isValidDeclaration(
return node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration';
}
-function filenameToId(path: UnknownFilePath, capitalize: boolean): string {
+export function filenameToId(
+ path: UnknownFilePath,
+ capitalize: boolean,
+): undefined | string {
let basename = path.getExtensionlessBasename();
if (basename === 'index') {
@@ -32,7 +35,7 @@ function filenameToId(path: UnknownFilePath, capitalize: boolean): string {
basename = path.getParent().getExtensionlessBasename();
}
- return toCamelCase(basename, capitalize);
+ return toVariableCamelCase(basename, capitalize);
}
export default {
@@ -62,7 +65,7 @@ export default {
declaration.type === 'FunctionDeclaration' ? 'function' : 'class';
const basename = filenameToId(context.path, type === 'class');
- if (basename !== id.name) {
+ if (basename !== undefined && basename !== id.name) {
const correctFilename = id.name + context.path.getExtensions();
return context.addFixableDiagnostic(

ADD
packages/@romejs/js-compiler/lint/rules/regular/importDefaultBasename.test.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {test} from 'rome';
+import {testLintMultiple} from '../testHelpers';
+
+test(
+ 'import default basename',
+ async (t) => {
+ await testLintMultiple(
+ t,
+ [
+ // INVALID
+ "import foo from './bar';",
+ // VALID
+ "import foo from './foo';",
+ ],
+ {category: 'lint/importDefaultBasename'},
+ );
+ },
+);

ADD
packages/@romejs/js-compiler/lint/rules/regular/importDefaultBasename.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {Path} from '@romejs/js-compiler';
+import {AnyNode} from '@romejs/js-ast';
+import {descriptions} from '@romejs/diagnostics';
+import {createUnknownFilePath} from '@romejs/path';
+import {filenameToId} from './defaultExportSameBasename';
+
+export default {
+ name: 'importDefaultBasename',
+ enter(path: Path): AnyNode {
+ const {node} = path;
+
+ if (node.type === 'ImportDeclaration') {
+ const {defaultSpecifier} = node;
+ if (defaultSpecifier === undefined) {
+ return node;
+ }
+
+ const expectedName = filenameToId(
+ createUnknownFilePath(node.source.value),
+ false,
+ );
+ if (expectedName === undefined) {
+ return node;
+ }
+
+ const localName = defaultSpecifier.local.name.name;
+ if (localName !== expectedName) {
+ path.context.addNodeDiagnostic(
+ node,
+ descriptions.LINT.IMPORT_DEFAULT_BASENAME(localName, expectedName),
+ );
+ }
+ }
+
+ return node;
+ },
+};

MODIFY
packages/@romejs/js-compiler/lint/rules/regular/inconsiderateLanguage.ts
packages/@romejs/js-compiler/lint/rules/regular/inconsiderateLanguage.ts
@@ -11,11 +11,14 @@ import {PositionTracker, SourceLocation} from '@romejs/parser-core';
import {ob1Coerce0} from '@romejs/ob1';
import {isIdentifierish} from '@romejs/js-ast-utils';
import {descriptions} from '@romejs/diagnostics';
-import terms from './inconsiderateLanguage.json';
+import inconsiderateLanguage from './inconsiderateLanguage.json';
import {preserveCasing} from '@romejs/string-utils';
// Fast regex for checking if we need to validate a string
-const regex = new RegExp(terms.map((term) => term.word).join('|'), 'gi');
+const regex = new RegExp(
+ inconsiderateLanguage.map((term) => term.word).join('|'),
+ 'gi',
+);
type CheckResult = {
loc: SourceLocation;
@@ -49,7 +52,7 @@ function check(
for (let i = 0; i < lower.length; i++) {
const char = lower[i];
- for (const {word, description, suggestion} of terms) {
+ for (const {word, description, suggestion} of inconsiderateLanguage) {
if (char === word[0] && lower.startsWith(word, i)) {
const wordWithSourceCasing = input.slice(i, i + word.length);

MODIFY
scripts/lint/add.cjs
scripts/lint/add.cjs
@@ -35,13 +35,12 @@ write(
* LICENSE file in the root directory of this source tree.
*/
-import {Path} from '@romejs/js-compiler';
-import {AnyNode} from '@romejs/js-ast';
+import {Path, TransformExitResult} from '@romejs/js-compiler';
import {descriptions} from '@romejs/diagnostics';
export default {
name: '${ruleName}',
- enter(path: Path): AnyNode {
+ enter(path: Path): TransformExitResult {
const {node} = path;
if (false) {

Add importDefaultBasename rule