@definitelytyped/dts-critic 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,21 +1,10 @@
1
1
  import yargs = require("yargs");
2
2
  import headerParser = require("@definitelytyped/header-parser");
3
3
  import fs = require("fs");
4
- import cp = require("child_process");
5
4
  import path = require("path");
6
- import semver = require("semver");
7
- import { sync as commandExistsSync } from "command-exists";
8
5
  import ts from "typescript";
9
- import * as tmp from "tmp";
10
- import which from "which";
11
6
 
12
7
  export enum ErrorKind {
13
- /** Declaration is marked as npm in header and has no matching npm package. */
14
- NoMatchingNpmPackage = "NoMatchingNpmPackage",
15
- /** Declaration has no npm package matching specified version. */
16
- NoMatchingNpmVersion = "NoMatchingNpmVersion",
17
- /** Declaration is not for an npm package, but has a name that conflicts with an existing npm package. */
18
- NonNpmHasMatchingPackage = "NonNpmHasMatchingPackage",
19
8
  /** Declaration needs to use `export =` to match the JavaScript module's behavior. */
20
9
  NeedsExportEquals = "NeedsExportEquals",
21
10
  /** Declaration has a default export, but JavaScript module does not have a default export. */
@@ -30,70 +19,31 @@ export enum ErrorKind {
30
19
  DtsSignatureNotInJs = "DtsSignatureNotInJs",
31
20
  }
32
21
 
33
- export enum Mode {
34
- /** Checks based only on the package name and on the declaration's DefinitelyTyped header. */
35
- NameOnly = "name-only",
36
- /** Checks based on the source JavaScript code, in addition to the checks performed in name-only mode. */
37
- Code = "code",
38
- }
39
-
40
- export function parseMode(mode: string): Mode | undefined {
41
- switch (mode) {
42
- case Mode.NameOnly:
43
- return Mode.NameOnly;
44
- case Mode.Code:
45
- return Mode.Code;
46
- }
47
- return undefined;
48
- }
49
-
50
- export type CheckOptions = NameOnlyOptions | CodeOptions;
51
- export interface NameOnlyOptions {
52
- mode: Mode.NameOnly;
53
- }
54
- export interface CodeOptions {
55
- mode: Mode.Code;
22
+ export interface CheckOptions {
56
23
  errors: Map<ExportErrorKind, boolean>;
57
24
  }
58
25
 
59
26
  export type ExportErrorKind = ExportError["kind"];
60
27
 
61
- const defaultOpts: CheckOptions = { mode: Mode.NameOnly };
62
-
63
28
  export function dtsCritic(
64
29
  dtsPath: string,
65
- sourcePath?: string,
66
- options: CheckOptions = defaultOpts,
30
+ sourcePath: string,
31
+ options: CheckOptions = { errors: new Map() },
67
32
  debug = false,
68
33
  ): CriticError[] {
69
- if (!commandExistsSync("tar")) {
70
- throw new Error(
71
- "You need to have tar installed to run dts-critic, you can get it from https://www.gnu.org/software/tar",
72
- );
73
- }
74
- if (!commandExistsSync("npm")) {
75
- throw new Error(
76
- "You need to have npm installed to run dts-critic, you can get it from https://www.npmjs.com/get-npm",
77
- );
34
+ if (!sourcePath && require.main !== module) {
35
+ // dtslint will issue an error.
36
+ return [];
78
37
  }
79
-
80
38
  const name = findDtsName(dtsPath);
81
39
  const packageJsonPath = path.join(path.dirname(path.resolve(dtsPath)), "package.json");
82
- const npmInfo = getNpmInfo(name);
83
40
  const header = parsePackageJson(name, JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")), path.dirname(dtsPath));
84
41
  if (header === undefined) {
85
42
  return [];
86
43
  } else if (header.nonNpm) {
87
44
  const errors: CriticError[] = [];
88
- const nonNpmError = checkNonNpm(name, npmInfo);
89
- if (nonNpmError) {
90
- errors.push(nonNpmError);
91
- }
92
-
93
45
  if (sourcePath) {
94
- if (options.mode === Mode.Code) {
95
- errors.push(...checkSource(name, dtsPath, sourcePath, options.errors, debug));
96
- }
46
+ errors.push(...checkSource(name, dtsPath, sourcePath, options.errors, debug));
97
47
  } else if (require.main === module) {
98
48
  console.log(`Warning: declaration provided is for a non-npm package.
99
49
  If you want to check the declaration against the JavaScript source code, you must provide a path to the source file.`);
@@ -101,30 +51,13 @@ If you want to check the declaration against the JavaScript source code, you mus
101
51
 
102
52
  return errors;
103
53
  } else {
104
- const npmVersion = checkNpm(name, npmInfo, header);
105
- if (typeof npmVersion !== "string") {
106
- return [npmVersion];
107
- }
108
-
109
- if (options.mode === Mode.Code) {
110
- let sourceEntry;
111
- let packagePath;
112
- if (sourcePath) {
113
- sourceEntry = sourcePath;
114
- } else {
115
- const tempDirName = tmp.dirSync({ unsafeCleanup: true }).name;
116
- packagePath = downloadNpmPackage(name, npmVersion, tempDirName);
117
- sourceEntry = require.resolve(path.resolve(packagePath));
118
- }
119
- const errors = checkSource(name, dtsPath, sourceEntry, options.errors, debug);
120
- if (packagePath) {
121
- // Delete the source afterward to avoid running out of space
122
- fs.rmSync(packagePath, { recursive: true, force: true });
123
- }
124
- return errors;
54
+ let sourceEntry;
55
+ if (!fs.statSync(sourcePath).isDirectory()) {
56
+ sourceEntry = sourcePath;
57
+ } else {
58
+ sourceEntry = require.resolve(path.resolve(sourcePath));
125
59
  }
126
-
127
- return [];
60
+ return checkSource(name, dtsPath, sourceEntry, options.errors, debug);
128
61
  }
129
62
  }
130
63
 
@@ -143,7 +76,7 @@ export const defaultErrors: ExportErrorKind[] = [ErrorKind.NeedsExportEquals, Er
143
76
  function main() {
144
77
  const argv = yargs
145
78
  .usage(
146
- "$0 --dts path-to-d.ts [--js path-to-source] [--mode mode] [--debug]\n\nIf source-folder is not provided, I will look for a matching package on npm.",
79
+ "$0 --dts path-to-d.ts --js path-to-source [--debug]\n\nIf source-folder is not provided, I will look for a matching package on npm.",
147
80
  )
148
81
  .option("dts", {
149
82
  describe: "Path of declaration file to be critiqued.",
@@ -154,12 +87,7 @@ function main() {
154
87
  describe: "Path of JavaScript file to be used as source.",
155
88
  type: "string",
156
89
  })
157
- .option("mode", {
158
- describe: "Mode defines what checks will be performed.",
159
- type: "string",
160
- default: Mode.NameOnly,
161
- choices: [Mode.NameOnly, Mode.Code],
162
- })
90
+ .demandOption("js", "Please provide a path to a JavaScript file for me to compare the d.ts against.")
163
91
  .option("debug", {
164
92
  describe: "Turn debug logging on.",
165
93
  type: "boolean",
@@ -168,14 +96,7 @@ function main() {
168
96
  .help()
169
97
  .parseSync();
170
98
 
171
- let opts;
172
- switch (argv.mode) {
173
- case Mode.NameOnly:
174
- opts = { mode: argv.mode };
175
- break;
176
- case Mode.Code:
177
- opts = { mode: argv.mode, errors: new Map() };
178
- }
99
+ const opts = { mode: argv.mode, errors: new Map() };
179
100
  const errors = dtsCritic(argv.dts, argv.js, opts, argv.debug);
180
101
  if (errors.length === 0) {
181
102
  console.log("No errors!");
@@ -186,104 +107,6 @@ function main() {
186
107
  }
187
108
  }
188
109
 
189
- const npmNotFound = "E404";
190
-
191
- export function getNpmInfo(name: string): NpmInfo {
192
- const npmName = dtToNpmName(name);
193
- const infoResult = cp.spawnSync(which.sync("npm"), ["info", npmName, "--json", "--silent", "versions", "dist-tags"], {
194
- encoding: "utf8",
195
- env: { ...process.env, COREPACK_ENABLE_STRICT: "0" },
196
- });
197
- const info = JSON.parse(infoResult.stdout || infoResult.stderr);
198
- if (info.error !== undefined) {
199
- const error = info.error as { code?: string; summary?: string };
200
- if (error.code === npmNotFound) {
201
- return { isNpm: false };
202
- } else {
203
- throw new Error(`Command 'npm info' for package ${npmName} returned an error. Reason: ${error.summary}.`);
204
- }
205
- } else if (infoResult.status !== 0) {
206
- throw new Error(`Command 'npm info' failed for package ${npmName} with status ${infoResult.status}.`);
207
- }
208
- return {
209
- isNpm: true,
210
- versions: Array.isArray(info.versions) ? info.versions : [info.versions],
211
- tags: info["dist-tags"] as { [tag: string]: string | undefined },
212
- };
213
- }
214
-
215
- /**
216
- * Checks DefinitelyTyped non-npm package.
217
- */
218
- function checkNonNpm(name: string, npmInfo: NpmInfo): NonNpmError | undefined {
219
- if (npmInfo.isNpm && !isExistingSquatter(name)) {
220
- return {
221
- kind: ErrorKind.NonNpmHasMatchingPackage,
222
- message: `The non-npm package '${name}' conflicts with the existing npm package '${dtToNpmName(name)}'.
223
- Try adding -browser to the end of the name to get
224
-
225
- ${name}-browser
226
- `,
227
- };
228
- }
229
- return undefined;
230
- }
231
-
232
- /**
233
- * Checks DefinitelyTyped npm package.
234
- * If all checks are successful, returns the npm version that matches the header.
235
- */
236
- function checkNpm(name: string, npmInfo: NpmInfo, header: headerParser.Header): NpmError | string {
237
- if (!npmInfo.isNpm) {
238
- return {
239
- kind: ErrorKind.NoMatchingNpmPackage,
240
- message: `Declaration file must have a matching npm package.
241
- To resolve this error, either:
242
- 1. Change the name to match an npm package.
243
- 2. Add \`"nonNpm": true\` to the package.json to indicate that this is not an npm package.
244
- Ensure the package name is descriptive enough to avoid conflicts with future npm packages.`,
245
- };
246
- }
247
- const target =
248
- header.libraryMajorVersion === 0 && header.libraryMinorVersion === 0
249
- ? undefined
250
- : `${header.libraryMajorVersion}.${header.libraryMinorVersion}`;
251
- const npmVersion = getMatchingVersion(target, npmInfo);
252
- if (!npmVersion) {
253
- const versions = npmInfo.versions;
254
- const verstring = versions.join(", ");
255
- const lateststring = versions[versions.length - 1];
256
- return {
257
- kind: ErrorKind.NoMatchingNpmVersion,
258
- message: `The types for '${name}' must match a version that exists on npm.
259
- You should copy the major and minor version from the package on npm.
260
-
261
- To resolve this error, change the version in the package.json, ${target},
262
- to match one on npm: ${verstring}.
263
-
264
- For example, if you're trying to match the latest version, use ${lateststring}.`,
265
- };
266
- }
267
- return npmVersion;
268
- }
269
-
270
- /**
271
- * Finds an npm version that matches the target version specified, if it exists.
272
- * If the target version is undefined, returns the latest version.
273
- * The npm version returned might be a prerelease version.
274
- */
275
- function getMatchingVersion(target: string | undefined, npmInfo: Npm): string | undefined {
276
- const versions = npmInfo.versions;
277
- if (target) {
278
- const matchingVersion = semver.maxSatisfying(versions, target, { includePrerelease: true });
279
- return matchingVersion || undefined;
280
- }
281
- if (npmInfo.tags.latest) {
282
- return npmInfo.tags.latest;
283
- }
284
- return versions[versions.length - 1];
285
- }
286
-
287
110
  /**
288
111
  * If dtsName is 'index' (as with DT) then look to the parent directory for the name.
289
112
  */
@@ -296,47 +119,6 @@ export function findDtsName(dtsPath: string) {
296
119
  return path.basename(path.dirname(resolved));
297
120
  }
298
121
 
299
- /** Returns path of downloaded npm package. */
300
- function downloadNpmPackage(name: string, version: string, outDir: string): string {
301
- const npmName = dtToNpmName(name);
302
- const fullName = `${npmName}@${version}`;
303
- const cpOpts = {
304
- encoding: "utf8",
305
- maxBuffer: 100 * 1024 * 1024,
306
- env: { ...process.env, COREPACK_ENABLE_STRICT: "0" },
307
- } as const;
308
- const npmPack = cp.execFileSync(which.sync("npm"), ["pack", fullName, "--json", "--silent"], cpOpts).trim();
309
- // https://github.com/npm/cli/issues/3405
310
- const tarballName = (npmPack.endsWith(".tgz") ? npmPack : (JSON.parse(npmPack)[0].filename as string))
311
- .replace(/^@/, "")
312
- .replace(/\//, "-");
313
- const outPath = path.join(outDir, name);
314
- initDir(outPath);
315
- const isBsdTar = cp.execFileSync(which.sync("tar"), ["--version"], cpOpts).includes("bsdtar");
316
- const args = isBsdTar
317
- ? ["-xz", "-f", tarballName, "-C", outPath]
318
- : ["-xz", "-f", tarballName, "-C", outPath, "--warning=none"];
319
- cp.execFileSync(which.sync("tar"), args, cpOpts);
320
- fs.unlinkSync(tarballName);
321
- return path.join(outPath, getPackageDir(outPath));
322
- }
323
-
324
- function getPackageDir(outPath: string): string {
325
- const dirs = fs.readdirSync(outPath, { encoding: "utf8", withFileTypes: true });
326
- for (const dirent of dirs) {
327
- if (dirent.isDirectory()) {
328
- return dirent.name;
329
- }
330
- }
331
- return "package";
332
- }
333
-
334
- function initDir(dirPath: string): void {
335
- if (!fs.existsSync(dirPath)) {
336
- fs.mkdirSync(dirPath, { recursive: true });
337
- }
338
- }
339
-
340
122
  export function checkSource(
341
123
  name: string,
342
124
  dtsPath: string,
@@ -839,18 +621,6 @@ function matches(srcFile: ts.SourceFile, predicate: (n: ts.Node) => boolean): bo
839
621
  return matchesNode(srcFile);
840
622
  }
841
623
 
842
- function isExistingSquatter(name: string) {
843
- return (
844
- name === "atom" ||
845
- name === "ember__string" ||
846
- name === "fancybox" ||
847
- name === "jsqrcode" ||
848
- name === "node" ||
849
- name === "geojson" ||
850
- name === "titanium"
851
- );
852
- }
853
-
854
624
  function isRealExportDefault(name: string) {
855
625
  return name.indexOf("react-native") > -1 || name === "ember-feature-flags" || name === "material-ui-datatables";
856
626
  }
@@ -894,14 +664,6 @@ export interface CriticError {
894
664
  position?: Position;
895
665
  }
896
666
 
897
- interface NpmError extends CriticError {
898
- kind: ErrorKind.NoMatchingNpmPackage | ErrorKind.NoMatchingNpmVersion;
899
- }
900
-
901
- interface NonNpmError extends CriticError {
902
- kind: ErrorKind.NonNpmHasMatchingPackage;
903
- }
904
-
905
667
  interface ExportEqualsError extends CriticError {
906
668
  kind: ErrorKind.NeedsExportEquals;
907
669
  }
@@ -967,18 +729,6 @@ interface DtsExportDiagnostics {
967
729
  defaultExport?: Position;
968
730
  }
969
731
 
970
- type NpmInfo = NonNpm | Npm;
971
-
972
- interface NonNpm {
973
- isNpm: false;
974
- }
975
-
976
- interface Npm {
977
- isNpm: true;
978
- versions: string[];
979
- tags: { [tag: string]: string | undefined };
980
- }
981
-
982
732
  type InferenceResult<T> = InferenceError | InferenceSuccess<T>;
983
733
 
984
734
  enum InferenceResultKind {
package/package.json CHANGED
@@ -1,31 +1,20 @@
1
1
  {
2
2
  "name": "@definitelytyped/dts-critic",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "author": "Nathan Shively-Sanders",
5
5
  "description": "Checks a new .d.ts against the Javascript source and tells you what problems it has",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "dependencies": {
10
- "command-exists": "^1.2.9",
11
- "semver": "^7.5.4",
12
- "tmp": "^0.2.1",
13
10
  "typescript": "^5.3.3",
14
- "which": "^4.0.0",
15
11
  "yargs": "^17.7.2",
16
- "@definitelytyped/header-parser": "0.2.1"
12
+ "@definitelytyped/header-parser": "0.2.2"
17
13
  },
18
14
  "peerDependencies": {
19
15
  "typescript": "*"
20
16
  },
21
- "devDependencies": {
22
- "@types/command-exists": "^1.2.3",
23
- "@types/semver": "^7.5.6",
24
- "@types/strip-json-comments": "^3.0.0",
25
- "@types/tmp": "^0.2.6",
26
- "@types/which": "^3.0.3",
27
- "strip-json-comments": "^3.1.1"
28
- },
17
+ "devDependencies": {},
29
18
  "main": "dist/index.js",
30
19
  "types": "dist/index.d.ts",
31
20
  "repository": {
@@ -50,7 +39,6 @@
50
39
  },
51
40
  "scripts": {
52
41
  "build": "tsc -b .",
53
- "dt": "node dist/dt.js",
54
42
  "test": "../../node_modules/.bin/jest --config ../../jest.config.js packages/dts-critic"
55
43
  }
56
44
  }