Skip to content
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
16 changes: 15 additions & 1 deletion docs/rules/format/name-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id: name-format
title: name-format
---

Enabling this rule will result in an error being generated if `name` is not lowercase.
Enabling this rule will result in an error being generated if `name` does not meet the [naming constraints](https://github.com/npm/validate-npm-package-name#naming-rules).

## Example .npmpackagejsonlintrc configuration

Expand All @@ -25,6 +25,18 @@ Enabling this rule will result in an error being generated if `name` is not lowe
}
```

```json
{
"name": "npm package json lint"
}
```

```json
{
"name": ".npm-package-json-lint"
}
```

### *Correct* example(s)

```json
Expand All @@ -35,4 +47,6 @@ Enabling this rule will result in an error being generated if `name` is not lowe

## History

* Augmented with all name checks in `validate-npm-package-name` in version 6.0.0
* Added checks for name length and not starting with . or _ in version 4.0.0
* Introduced in version 1.0.0
29 changes: 28 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"plur": "^4.0.0",
"semver": "^7.3.5",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
"strip-json-comments": "^3.1.1",
"validate-npm-package-name": "^3.0.0"
},
"devDependencies": {
"eslint": "^7.32.0",
Expand Down
17 changes: 5 additions & 12 deletions src/rules/name-format.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
const {isLowercase} = require('../validators/format');
const validateName = require('validate-npm-package-name');
const LintIssue = require('../LintIssue');
const getNameError = require('../utils/getNameError');

const lintId = 'name-format';
const nodeName = 'name';
const ruleType = 'standard';
const maxLength = 214;

const lint = (packageJsonData, severity) => {
if (!packageJsonData.hasOwnProperty(nodeName)) {
return true;
}

const name = packageJsonData[nodeName];
const results = validateName(name);

if (!isLowercase(name)) {
return new LintIssue(lintId, severity, nodeName, 'Format should be all lowercase');
}

if (name.length > maxLength) {
return new LintIssue(lintId, severity, nodeName, `name should be less than or equal to ${maxLength} characters.`);
}

if (name.startsWith('.') || name.startsWith('_')) {
return new LintIssue(lintId, severity, nodeName, 'name should not start with . or _');
if (!results.validForNewPackages) {
return new LintIssue(lintId, severity, nodeName, getNameError(results));
}

return true;
Expand Down
22 changes: 22 additions & 0 deletions src/utils/getNameError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Gets the error message indicating why the name is invalid.
*
* @param {object} results Results from validate-npm-package-name
* @returns {string} Error/warning message
*/
const getNameError = (results) => {
// Errors are returned for names that were never valid
if (results.errors && results.errors.length > 0) {
return results.errors[0];
}

// Warnings are returned for names that are no longer valid
if (results.warnings && results.warnings.length > 0) {
return results.warnings[0];
}

// Ensure that an error message is returned in any case
return 'name invalid';
};

module.exports = getNameError;
8 changes: 0 additions & 8 deletions src/validators/format.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
const semver = require('semver');

/**
* Determines whether or not the string is lowercase
* @param {string} name Name
* @return {boolean} True if the string is lowercase or is missing. False if it is not.
*/
const isLowercase = (name) => name === name.toLowerCase();

/**
* Determines whether or not the node's value is a valid semantic version
* @param {object} packageJsonData Valid JSON
Expand All @@ -22,6 +15,5 @@ const isValidVersionNumber = (packageJsonData, nodeName) => {
};

module.exports = {
isLowercase,
isValidVersionNumber,
};
55 changes: 48 additions & 7 deletions test/unit/rules/name-format.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,34 @@ describe('name-format Unit Tests', () => {
expect(response.lintId).toStrictEqual('name-format');
expect(response.severity).toStrictEqual('error');
expect(response.node).toStrictEqual('name');
expect(response.lintMessage).toStrictEqual('Format should be all lowercase');
expect(response.lintMessage).toStrictEqual('name can no longer contain capital letters');
});

test('contains space - LintIssue object should be returned', () => {
const packageJsonData = {
name: 'contains space',
};
const response = lint(packageJsonData, 'error');

expect(response.lintId).toStrictEqual('name-format');
expect(response.severity).toStrictEqual('error');
expect(response.node).toStrictEqual('name');
expect(response.lintMessage).toStrictEqual('name can only contain URL-friendly characters');
});

test('exceeds max length - LintIssue object should be returned', () => {
const packageJsonData = {
name: 'a'.padStart(215),
name: 'a'.padStart(215, 'a'),
};
const response = lint(packageJsonData, 'error');

expect(response.lintId).toStrictEqual('name-format');
expect(response.severity).toStrictEqual('error');
expect(response.node).toStrictEqual('name');
expect(response.lintMessage).toStrictEqual('name should be less than or equal to 214 characters.');
expect(response.lintMessage).toStrictEqual('name can no longer contain more than 214 characters');
});

test('starts with . - LintIssue object should be returned', () => {
test('starts with . and no scope - LintIssue object should be returned', () => {
const packageJsonData = {
name: '.lowercase',
};
Expand All @@ -43,10 +55,10 @@ describe('name-format Unit Tests', () => {
expect(response.lintId).toStrictEqual('name-format');
expect(response.severity).toStrictEqual('error');
expect(response.node).toStrictEqual('name');
expect(response.lintMessage).toStrictEqual('name should not start with . or _');
expect(response.lintMessage).toStrictEqual('name cannot start with a period');
});

test('starts with _ - LintIssue object should be returned', () => {
test('starts with _ and no scope - LintIssue object should be returned', () => {
const packageJsonData = {
name: '_lowercase',
};
Expand All @@ -55,7 +67,36 @@ describe('name-format Unit Tests', () => {
expect(response.lintId).toStrictEqual('name-format');
expect(response.severity).toStrictEqual('error');
expect(response.node).toStrictEqual('name');
expect(response.lintMessage).toStrictEqual('name should not start with . or _');
expect(response.lintMessage).toStrictEqual('name cannot start with an underscore');
});
});

describe('when package.json has node with correct format', () => {
test('all valid characters - true should be returned', () => {
const packageJsonData = {
name: 'lowercase-name',
};
const response = lint(packageJsonData, 'error');

expect(response).toBe(true);
});

test('starts with . and has scope - true should be returned', () => {
const packageJsonData = {
name: '@foo/.lowercase',
};
const response = lint(packageJsonData, 'error');

expect(response).toBe(true);
});

test('starts with _ and has scope - true should be returned', () => {
const packageJsonData = {
name: '@foo/_lowercase',
};
const response = lint(packageJsonData, 'error');

expect(response).toBe(true);
});
});

Expand Down
38 changes: 38 additions & 0 deletions test/unit/utils/getNameError.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const getNameError = require('../../../src/utils/getNameError');

const genericErrorMessage = 'name invalid';
const getResults = (includeErrors = true, includeWarnings = true) => {
const results = {
validForNewPackages: false,
validForOldPackages: !includeErrors,
warnings: ['first warning', 'second warning'],
errors: ['first error', 'second error'],
};

if (!includeErrors) {
delete results.errors;
}

if (!includeWarnings) {
delete results.warnings;
}

return results;
};

describe('getNameError Unit Tests', () => {
test('if errors - returns first error', () => {
const results = getResults(true, true);
expect(getNameError(results)).toBe(results.errors[0]);
});

test('if warnings and no errors - returns first warning', () => {
const results = getResults(false, true);
expect(getNameError(results)).toBe(results.warnings[0]);
});

test('if no warnings and no errors - returns generic error message', () => {
const results = getResults(false, false);
expect(getNameError(results)).toBe(genericErrorMessage);
});
});
20 changes: 0 additions & 20 deletions test/unit/validators/format.test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,6 @@
const format = require('../../../src/validators/format');

describe('format Unit Tests', () => {
describe('isLowercase method', () => {
describe('when the string is lowercase', () => {
test('true should be returned', () => {
const string = 'awesome-module';
const response = format.isLowercase(string);

expect(response).toBe(true);
});
});

describe('when the string is not lowercase', () => {
test('false should be returned', () => {
const string = 'aweSome-moDule';
const response = format.isLowercase(string);

expect(response).toBe(false);
});
});
});

describe('isValidVersionNumber method', () => {
describe('when the node does not exist in the package.json file', () => {
test('true should be returned', () => {
Expand Down