|
const path = require("path"); |
|
const fs = require("fs"); |
|
|
|
module.exports = { |
|
meta: { |
|
type: "problem", |
|
docs: { |
|
description: "Enforce shared types for multi-platform components", |
|
category: "Possible Errors", |
|
recommended: true, |
|
}, |
|
fixable: null, |
|
}, |
|
create(context) { |
|
return { |
|
Program(node) { |
|
const filename = context.getFilename(); |
|
const ext = path.extname(filename); |
|
const fullBasename = path.basename(filename, ext); |
|
const dirname = path.dirname(filename); |
|
|
|
if (!ext.endsWith("tsx")) { |
|
return; |
|
} |
|
|
|
// Remove platform-specific extension if present |
|
const basename = fullBasename.replace( |
|
/\.(native|web|ios|android)$/, |
|
"" |
|
); |
|
|
|
// Check for the existence of any variant (including the base file) |
|
const variants = [ |
|
path.join(dirname, `${basename}.tsx`), |
|
...platformExts.map((ext) => path.join(dirname, `${basename}${ext}`)), |
|
]; |
|
|
|
const existingVariants = variants.filter((v) => checkExists(v)); |
|
const isMultiPlatform = existingVariants.length > 1; |
|
|
|
if (isMultiPlatform) { |
|
// Check if types file exists |
|
const typesFile = path.join(dirname, `${basename}.types.ts`); |
|
if (checkExists(typesFile)) { |
|
context.report({ |
|
node, |
|
message: `Multi-platform component "${basename}" should have a separate types file "${basename}.types.ts"`, |
|
}); |
|
} else { |
|
// Check if types are imported in the current file |
|
checkTypesImport(node, basename, context.report); |
|
} |
|
} |
|
}, |
|
}; |
|
}, |
|
}; |
|
|
|
// Platform extensions to check |
|
const platformExts = [".native.tsx", ".web.tsx", ".ios.tsx", ".android.tsx"]; |
|
|
|
function checkTypesImport(node, basename, report) { |
|
const hasTypesImport = node.body.some( |
|
(n) => |
|
n.type === "ImportDeclaration" && n.source.value === `./${basename}.types` |
|
); |
|
if (!hasTypesImport) { |
|
report({ |
|
node, |
|
message: `Component "${basename}" should import types from "${basename}.types.ts"`, |
|
}); |
|
} |
|
} |
|
|
|
const cache = new Map(); |
|
function checkExists(path) { |
|
if (cache.has(path)) { |
|
return cache.get(path); |
|
} |
|
|
|
const exists = fs.existsSync(path); |
|
cache.set(path, exists); |
|
return exists; |
|
} |