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