From 5f70596197614408d3a4c566a89e84e026f8631c Mon Sep 17 00:00:00 2001 From: sverweij Date: Sat, 24 Apr 2021 21:02:32 +0200 Subject: [PATCH] feature(extract): adds basic support for ESM urls --- src/extract/get-dependencies.js | 6 +- src/extract/utl/extract-module-attributes.js | 37 +++++++++++++ src/schema/cruise-result.schema.json | 8 +++ .../utl/extract-module-attributes.spec.js | 55 +++++++++++++++++++ ...tract-line-coverage-from-istanbul-json.mjs | 3 +- tools/generate-schemas.utl.mjs | 3 +- tools/schema/dependencies.mjs | 16 ++++++ types/cruise-result.d.ts | 17 ++++++ 8 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 src/extract/utl/extract-module-attributes.js create mode 100644 test/extract/utl/extract-module-attributes.spec.js diff --git a/src/extract/get-dependencies.js b/src/extract/get-dependencies.js index 976c513b0b..333bb2366b 100644 --- a/src/extract/get-dependencies.js +++ b/src/extract/get-dependencies.js @@ -12,6 +12,7 @@ const toJavascriptAST = require("./parse/to-javascript-ast"); const toTypescriptAST = require("./parse/to-typescript-ast"); const toSwcAST = require("./parse/to-swc-ast"); const detectPreCompilationNess = require("./utl/detect-pre-compilation-ness"); +const extractModuleAttributes = require("./utl/extract-module-attributes"); function extractFromSwcAST(pOptions, pFileName) { return extractSwcDeps( @@ -101,7 +102,10 @@ function extractDependencies(pCruiseOptions, pFileName, pTranspileOptions) { ); } - return lDependencies; + return lDependencies.map((pDependency) => ({ + ...pDependency, + ...extractModuleAttributes(pDependency.module), + })); } function matchesDoNotFollow(pResolved, pDoNotFollow) { diff --git a/src/extract/utl/extract-module-attributes.js b/src/extract/utl/extract-module-attributes.js new file mode 100644 index 0000000000..81c4d64029 --- /dev/null +++ b/src/extract/utl/extract-module-attributes.js @@ -0,0 +1,37 @@ +/* eslint-disable security/detect-object-injection */ + +/** + * Given a module string returns in an object + * - the module name + * - the protocol (when encoded in the string) + * - the mimeType (when encoded in the string) + * + * See https://nodejs.org/api/esm.html#esm_urls + * + * would've loved to use url.URL here, but that doesn't extract the mime type + * (if there's a default node API that does this I'm all ears) + * + * @param {string} pString + * @returns {any} + */ +module.exports = function extractModuleAttributes(pString) { + let lReturnValue = { module: pString }; + const lModuleAttributes = pString.match( + // eslint-disable-next-line security/detect-unsafe-regex, unicorn/no-unsafe-regex + /^(node:|file:|data:)?(([^,]+),)?(.+)$/ + ); + const lProtocolPosition = 1; + const lMimeTypePosition = 3; + const lModulePosition = 4; + + if (lModuleAttributes) { + lReturnValue.module = lModuleAttributes[lModulePosition]; + if (lModuleAttributes[lProtocolPosition]) { + lReturnValue.protocol = lModuleAttributes[lProtocolPosition]; + } + if (lModuleAttributes[lMimeTypePosition]) { + lReturnValue.mimeType = lModuleAttributes[lMimeTypePosition]; + } + } + return lReturnValue; +}; diff --git a/src/schema/cruise-result.schema.json b/src/schema/cruise-result.schema.json index 6cf0ceb5c9..3447d22717 100644 --- a/src/schema/cruise-result.schema.json +++ b/src/schema/cruise-result.schema.json @@ -163,6 +163,14 @@ "type": "string", "description": "The name of the module as it appeared in the source code, e.g. './main'" }, + "protocol": { + "type": "string", + "description": "If the module specification is an URI with a protocol in it (e.g. `import * as fs from 'node:fs'` or `import stuff from 'data:application/json,some-thing'`) - this attribute holds the protocol part (e.g. 'node', 'data', 'file')" + }, + "mimeType": { + "type": "string", + "description": "If the module specification is an URI and contains a mime type, this attribute holds the mime type (e.g. in `import stuff from 'data:application/json,some-thing'`this would be data:application/json" + }, "resolved": { "type": "string", "description": "The (resolved) file name of the module, e.g. 'src/main/index.js'" diff --git a/test/extract/utl/extract-module-attributes.spec.js b/test/extract/utl/extract-module-attributes.spec.js new file mode 100644 index 0000000000..08e759fca7 --- /dev/null +++ b/test/extract/utl/extract-module-attributes.spec.js @@ -0,0 +1,55 @@ +const expect = require("chai").expect; +const extractModuleAttributes = require("../../../src/extract/utl/extract-module-attributes"); + +describe("extract/utl/extract-module-attributes", () => { + it("leaves regular module specifications alone", () => { + expect(extractModuleAttributes("protodash")).to.deep.equal({ + module: "protodash", + }); + }); + + it("extracts the protocol if there is one", () => { + expect(extractModuleAttributes("node:fs")).to.deep.equal({ + module: "fs", + protocol: "node:", + }); + }); + + it("leaves things alone the protocol is unknown", () => { + expect(extractModuleAttributes("nod:fs")).to.deep.equal({ + module: "nod:fs", + }); + }); + + it("manages empty strings gracefully", () => { + expect(extractModuleAttributes("")).to.deep.equal({ + module: "", + }); + }); + + it("extracts both protocol and mimeType when they're in the URI", () => { + expect( + extractModuleAttributes("data:application/json,gegevens.json") + ).to.deep.equal({ + module: "gegevens.json", + protocol: "data:", + mimeType: "application/json", + }); + }); + + it("handles emtpy mimeTypes gracefulley", () => { + expect(extractModuleAttributes("data:,gegevens.json")).to.deep.equal({ + module: ",gegevens.json", + protocol: "data:", + }); + }); + + it("when protocol separator is mistyped, returns it as part of the module name", () => { + expect( + extractModuleAttributes("data:application/json;gegevens.json") + ).to.deep.equal({ + module: "application/json;gegevens.json", + protocol: "data:", + }); + }); +}); diff --git a/tools/extract-line-coverage-from-istanbul-json.mjs b/tools/extract-line-coverage-from-istanbul-json.mjs index 580905e12f..80ab678fa0 100644 --- a/tools/extract-line-coverage-from-istanbul-json.mjs +++ b/tools/extract-line-coverage-from-istanbul-json.mjs @@ -1,4 +1,5 @@ -import { EOL } from "os"; +// eslint-disable-next-line node/no-missing-import, import/no-unresolved +import { EOL } from "node:os"; import getStream from "get-stream"; const DECIMAL_BASE = 10; diff --git a/tools/generate-schemas.utl.mjs b/tools/generate-schemas.utl.mjs index 02df3e1d77..141703d3af 100644 --- a/tools/generate-schemas.utl.mjs +++ b/tools/generate-schemas.utl.mjs @@ -1,4 +1,5 @@ -import fs from "fs"; +// eslint-disable-next-line node/no-missing-import, import/no-unresolved +import fs from "node:fs"; import prettier from "prettier"; function jsonTheSchema(pOutputFileName) { diff --git a/tools/schema/dependencies.mjs b/tools/schema/dependencies.mjs index 7ffa7d48cc..3c9fe6d791 100644 --- a/tools/schema/dependencies.mjs +++ b/tools/schema/dependencies.mjs @@ -30,6 +30,22 @@ export default { description: "The name of the module as it appeared in the source code, e.g. './main'", }, + protocol: { + type: "string", + description: + "If the module specification is an URI with a protocol in it (e.g. " + + "`import * as fs from 'node:fs'` or " + + "`import stuff from 'data:application/json,some-thing'`) - this attribute " + + "holds the protocol part (e.g. 'node:', 'data:', 'file:'). Also see " + + "https://nodejs.org/api/esm.html#esm_urls", + }, + mimeType: { + type: "string", + description: + "If the module specification is an URI and contains a mime type, this " + + "attribute holds the mime type (e.g. in `import stuff from 'data:application/json,some-thing'` " + + "this would be data:application/json). Also see https://nodejs.org/api/esm.html#esm_urls", + }, resolved: { type: "string", description: diff --git a/types/cruise-result.d.ts b/types/cruise-result.d.ts index b5867da6a5..c0f79a052b 100644 --- a/types/cruise-result.d.ts +++ b/types/cruise-result.d.ts @@ -163,6 +163,23 @@ export interface IDependency { * The name of the module as it appeared in the source code, e.g. './main' */ module: string; + /** + * If the module specification is an URI with a protocol in it (e.g. + * `import * as fs from 'node:fs'` or + * `import stuff from 'data:application/json,some-thing'`) - + * this attribute holds the protocol part (e.g. 'node:', 'data:', 'file:'). + * + * Also see https://nodejs.org/api/esm.html#esm_urls + */ + protocol: string; + /** + * If the module specification is an URI and contains a mime type, this + * attribute holds the mime type (e.g. in `import stuff from 'data:application/json,some-thing' + * `this would be data:application/json) + * + * Also see https://nodejs.org/api/esm.html#esm_urls + */ + mimeType: string; moduleSystem: ModuleSystemType; /** * The (resolved) file name of the module, e.g. 'src/main//index.js'