• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ext / npm-pkg-lint / 19153649309

07 Nov 2025 12:03AM UTC coverage: 93.718% (+0.2%) from 93.535%
19153649309

push

github

ext
feat: new rule `conflicting-types-typings`

315 of 344 branches covered (91.57%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

610 of 643 relevant lines covered (94.87%)

85.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.7
/src/package-json.ts
1
import { type DocumentNode } from "@humanwhocodes/momoa";
2
import { type Message } from "./message";
3
import { type Result } from "./result";
4
import { conflictingTypesTypings } from "./rules/conflicting-types-typings";
2✔
5
import { deprecatedDependency } from "./rules/deprecated-dependency";
2✔
6
import { isDisallowedDependency } from "./rules/disallowed-dependency";
2✔
7
import { exportsTypesOrder } from "./rules/exports-types-order";
2✔
8
import { isObsoleteDependency } from "./rules/obsolete-dependency";
2✔
9
import { outdatedEngines } from "./rules/outdated-engines";
2✔
10
import { preferTypes } from "./rules/prefer-types";
2✔
11
import { shadowedTypes } from "./rules/shadowed-types";
2✔
12
import { typesNodeMatchingEngine } from "./rules/types-node-matching-engine";
2✔
13
import { verifyEngineConstraint } from "./rules/verify-engine-constraint";
2✔
14
import { type PackageJson } from "./types";
15
import { jsonLocation } from "./utils";
2✔
16
import {
2✔
17
        ValidationError,
18
        nonempty,
19
        present,
20
        typeArray,
21
        typeString,
22
        validRepoUrl,
23
        validUrl,
24
} from "./validators";
25

26
export interface VerifyPackageJsonOptions {
27
        allowedDependencies: Set<string>;
28
        allowTypesDependencies?: boolean | undefined;
29
        ignoreMissingFields?: boolean | undefined;
30
        ignoreNodeVersion: boolean | number;
31
}
32

33
type validator = (key: string, value: unknown) => void;
34

35
const fields: Record<string, validator[]> = {
2✔
36
        description: [present, typeString, nonempty],
37
        keywords: [present, typeArray, nonempty],
38
        homepage: [present, typeString, validUrl],
39
        bugs: [present, validUrl],
40
        license: [present, typeString, nonempty],
41
        author: [present, nonempty],
42
        repository: [present, validRepoUrl],
43
};
44

45
function verifyFields(
46
        pkg: PackageJson,
47
        pkgAst: DocumentNode,
48
        options: VerifyPackageJsonOptions,
49
): Message[] {
50
        const messages: Message[] = [];
38✔
51

52
        for (const [field, validators] of Object.entries(fields)) {
38✔
53
                try {
266✔
54
                        for (const validator of validators) {
266✔
55
                                validator(field, pkg[field]);
654✔
56
                        }
57
                } catch (error) {
58
                        // istanbul ignore next
59
                        if (!(error instanceof ValidationError)) {
60
                                throw error;
61
                        }
62
                        if (error.validator === present.name && options.ignoreMissingFields) {
37✔
63
                                continue;
1✔
64
                        }
65
                        const { line, column } =
66
                                error.validator !== present.name
36✔
67
                                        ? jsonLocation(pkgAst, "value", field)
68
                                        : { line: 1, column: 1 };
69
                        if (error instanceof Error) {
36!
70
                                messages.push({
36✔
71
                                        ruleId: "package-json-fields",
72
                                        severity: 2,
73
                                        message: error.message,
74
                                        line,
75
                                        column,
76
                                });
77
                        }
78
                }
79
        }
80

81
        return messages;
38✔
82
}
83

84
function getActualDependency(key: string, version: string): string {
85
        /* handle npm: prefix */
86
        if (version.startsWith("npm:")) {
7✔
87
                const [name] = version.slice("npm:".length).split("@", 2);
2✔
88
                return name;
2✔
89
        }
90

91
        return key;
5✔
92
}
93

94
/* eslint-disable-next-line complexity -- technical debt */
95
function verifyDependencies(
96
        pkg: PackageJson,
97
        pkgAst: DocumentNode,
98
        options: VerifyPackageJsonOptions,
99
): Message[] {
100
        const messages: Message[] = [];
38✔
101
        const { dependencies = {}, devDependencies = {}, peerDependencies = {} } = pkg;
38✔
102

103
        for (const [key, version] of Object.entries(dependencies)) {
38✔
104
                const dependency = getActualDependency(key, version);
7✔
105

106
                /* skip dependencies explicitly allowed by the user */
107
                if (options.allowedDependencies.has(dependency)) {
7✔
108
                        continue;
2✔
109
                }
110

111
                /* skip @types/* if explicitly allowed by user */
112
                if (options.allowTypesDependencies && /^@types\//.exec(dependency)) {
5✔
113
                        continue;
1✔
114
                }
115

116
                if (isDisallowedDependency(pkg, dependency)) {
4✔
117
                        const { line, column } = jsonLocation(pkgAst, "member", "dependencies", key);
3✔
118
                        const name = key === dependency ? `"${dependency}"` : `"${key}" ("npm:${dependency}")`;
3✔
119
                        messages.push({
3✔
120
                                ruleId: "disallowed-dependency",
121
                                severity: 2,
122
                                message: `${name} should be a devDependency`,
123
                                line,
124
                                column,
125
                        });
126
                }
127
        }
128

129
        function verifyObsolete(
130
                dependency: string,
131
                source: "dependencies" | "devDependencies" | "peerDependencies",
132
        ): void {
133
                const obsolete = isObsoleteDependency(dependency);
8✔
134
                if (obsolete) {
8✔
135
                        const { line, column } = jsonLocation(pkgAst, "member", source, dependency);
1✔
136
                        messages.push({
1✔
137
                                ruleId: "obsolete-dependency",
138
                                severity: 2,
139
                                message: `"${dependency}" is obsolete and should no longer be used: ${obsolete.message}`,
140
                                line,
141
                                column,
142
                        });
143
                }
144
        }
145

146
        for (const dependency of Object.keys(dependencies)) {
38✔
147
                verifyObsolete(dependency, "dependencies");
7✔
148
        }
149

150
        for (const dependency of Object.keys(devDependencies)) {
38✔
151
                verifyObsolete(dependency, "devDependencies");
1✔
152
        }
153

154
        for (const dependency of Object.keys(peerDependencies)) {
38✔
UNCOV
155
                verifyObsolete(dependency, "peerDependencies");
×
156
        }
157

158
        return messages;
38✔
159
}
160

161
export async function verifyPackageJson(
2✔
162
        pkg: PackageJson,
163
        pkgAst: DocumentNode,
164
        filePath: string,
165
        options: VerifyPackageJsonOptions = { allowedDependencies: new Set(), ignoreNodeVersion: false },
30✔
166
): Promise<Result[]> {
167
        const { ignoreNodeVersion } = options;
38✔
168

169
        const messages: Message[] = [
38✔
170
                ...conflictingTypesTypings(pkg, pkgAst),
171
                ...(await deprecatedDependency(pkg, pkgAst, options)),
172
                ...(await verifyEngineConstraint(pkg)),
173
                ...exportsTypesOrder(pkg, pkgAst),
174
                ...verifyFields(pkg, pkgAst, options),
175
                ...verifyDependencies(pkg, pkgAst, options),
176
                ...outdatedEngines(pkg, pkgAst, ignoreNodeVersion),
177
                ...preferTypes(pkg, pkgAst),
178
                ...shadowedTypes(pkg, pkgAst),
179
                ...typesNodeMatchingEngine(pkg, pkgAst),
180
        ];
181

182
        if (messages.length === 0) {
38✔
183
                return [];
9✔
184
        }
185

186
        return [
29✔
187
                {
188
                        messages,
189
                        filePath,
190
                        errorCount: messages.filter((it) => it.severity === 2).length,
41✔
191
                        warningCount: messages.filter((it) => it.severity === 1).length,
41✔
192
                        fixableErrorCount: 0,
193
                        fixableWarningCount: 0,
194
                },
195
        ];
196
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc