Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix no-missing-import for typescript #24

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
40 changes: 6 additions & 34 deletions lib/rules/file-extension-in-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@

const path = require("path")
const fs = require("fs")
const mapTypescriptExtension = require("../util/map-typescript-extension.js")
const visitImport = require("../util/visit-import")
const packageNamePattern = /^(?:@[^/\\]+[/\\])?[^/\\]+$/u
const corePackageOverridePattern =
/^(?:assert|async_hooks|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|http2|https|inspector|module|net|os|path|perf_hooks|process|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|trace_events|tty|url|util|v8|vm|worker_threads|zlib)[/\\]$/u
const typescriptFileExtensionsMapping = {
".ts": ".js",
".cts": ".cjs",
".mts": ".mjs",
}

/**
* Get all file extensions of the files which have the same basename.
Expand All @@ -36,27 +32,6 @@ function getExistingExtensions(filePath) {
}
}

/**
* Get the file extension that should be added in an import statement,
* based on the given file extension of the referenced file.
*
* For example, in typescript, when referencing another typescript from a typescript file,
* a .js extension should be used instead of the original .ts extension of the referenced file.
* @param {string} referencedFileExt The original file extension of the referenced file.
* @param {string} referencingFileExt The original file extension of the file the contains the import statement.
* @returns {string} The file extension to append to the import statement.
*/
function getFileExtensionToAdd(referencedFileExt, referencingFileExt) {
if (
referencingFileExt in typescriptFileExtensionsMapping &&
referencedFileExt in typescriptFileExtensionsMapping
) {
return typescriptFileExtensionsMapping[referencedFileExt]
}

return referencedFileExt
}

module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -104,19 +79,16 @@ module.exports = {

// Get extension.
const originalExt = path.extname(name)
const resolvedExt = path.extname(filePath)
const existingExts = getExistingExtensions(filePath)
const ext = resolvedExt || existingExts.join(" or ")
const ext = path.extname(filePath) || existingExts.join(" or ")
const style = overrideStyle[ext] || defaultStyle

// Verify.
if (style === "always" && ext !== originalExt) {
const referencingFileExt = path.extname(
context.getPhysicalFilename()
)
const fileExtensionToAdd = getFileExtensionToAdd(
ext,
referencingFileExt
const fileExtensionToAdd = mapTypescriptExtension(
context,
filePath,
ext
)
context.report({
node,
Expand Down
21 changes: 18 additions & 3 deletions lib/util/check-existence.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
*/
"use strict"

const path = require("path")
const exists = require("./exists")
const getAllowModules = require("./get-allow-modules")
const isTypescript = require("./is-typescript.js")
const mapTypescriptExtension = require("../util/map-typescript-extension.js")

/**
* Checks whether or not each requirement target exists.
Expand All @@ -17,16 +20,28 @@ const getAllowModules = require("./get-allow-modules")
* @param {ImportTarget[]} targets - A list of target information to check.
* @returns {void}
*/
module.exports = function checkForExistence(context, targets) {
module.exports = function checkExistence(context, targets) {
const allowed = new Set(getAllowModules(context))

for (const target of targets) {
const missingModule =
target.moduleName != null &&
!allowed.has(target.moduleName) &&
target.filePath == null
const missingFile =
target.moduleName == null && !exists(target.filePath)

let missingFile = target.moduleName == null && !exists(target.filePath)
if (missingFile && isTypescript(context)) {
const parsed = path.parse(target.filePath)
const reversedExt = mapTypescriptExtension(
context,
target.filePath,
parsed.ext,
true
)
const reversedPath =
path.resolve(parsed.dir, parsed.name) + reversedExt
missingFile = target.moduleName == null && !exists(reversedPath)
}

if (missingModule || missingFile) {
context.report({
Expand Down
16 changes: 16 additions & 0 deletions lib/util/is-typescript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use strict"

const path = require("path")

const typescriptExtensions = [".ts", ".cts", ".mts"]

/**
* Determine if the context source file is typescript.
*
* @param {RuleContext} context - A context
* @returns {boolean}
*/
module.exports = function isTypescript(context) {
const sourceFileExt = path.extname(context.getPhysicalFilename())
return typescriptExtensions.includes(sourceFileExt)
}
49 changes: 49 additions & 0 deletions lib/util/map-typescript-extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use strict"

const path = require("path")
const isTypescript = require("../util/is-typescript")

const mapping = {
".ts": ".js",
".cts": ".cjs",
".mts": ".mjs",
}

const reverseMapping = {
".js": ".ts",
".cjs": ".cts",
".mjs": ".mts",
}

/**
* Maps the typescript file extension that should be added in an import statement,
* based on the given file extension of the referenced file OR fallsback to the original given extension.
*
* For example, in typescript, when referencing another typescript from a typescript file,
* a .js extension should be used instead of the original .ts extension of the referenced file.
*
* @param {RuleContext} context
* @param {string} filePath The filePath of the import
* @param {string} fallbackExtension The non-typescript fallback
* @param {boolean} reverse Execute a reverse path mapping
* @returns {string} The file extension to append to the import statement.
*/
module.exports = function mapTypescriptExtension(
context,
filePath,
fallbackExtension,
reverse = false
) {
const ext = path.extname(filePath)
if (reverse) {
if (isTypescript(context) && ext in reverseMapping) {
return reverseMapping[ext]
}
} else {
if (isTypescript(context) && ext in mapping) {
return mapping[ext]
}
}

return fallbackExtension
}
Empty file added tests/fixtures/no-missing/d.ts
Empty file.
13 changes: 13 additions & 0 deletions tests/lib/rules/no-missing-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ ruleTester.run("no-missing-import", rule, {
filename: fixture("test.js"),
code: "import a from './a.js';",
},
{
filename: fixture("test.ts"),
code: "import a from './a.js';",
},
{
filename: fixture("test.ts"),
code: "import a from './d.ts';",
},
{
filename: fixture("test.js"),
code: "import aConfig from './a.config.js';",
Expand Down Expand Up @@ -166,6 +174,11 @@ ruleTester.run("no-missing-import", rule, {
code: "import c from './c';",
errors: ['"./c" is not found.'],
},
{
filename: fixture("test.ts"),
code: "import d from './d';",
errors: ['"./d" is not found.'],
},
{
filename: fixture("test.js"),
code: "import d from './d';",
Expand Down