-
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
335 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
Feature: include dynamic imports | ||
|
||
Scenario: Include dynamic import as promise | ||
Given file "a.ts" is | ||
""" | ||
export default type A = 1; | ||
export type A_unused = 2; | ||
""" | ||
And file "b.ts" is | ||
""" | ||
import("./a").then(A_imported => { | ||
console.log(A_imported); | ||
}); | ||
export const B_unused: A = 0 | ||
""" | ||
When analyzing "tsconfig.json" | ||
Then the result is { "b.ts": ["B_unused"], "a.ts": ["A_unused"] } | ||
|
||
# TODO xxx fix | ||
Scenario: Include dynamic import as promise - in a function | ||
Given file "a.ts" is | ||
""" | ||
export default type A = 1; | ||
export type A_unused = 2; | ||
""" | ||
And file "b.ts" is | ||
""" | ||
function imports() { | ||
import("./a").then(A_imported => { | ||
console.log(A_imported); | ||
}); | ||
} | ||
export const B_unused: A = 0 | ||
""" | ||
When analyzing "tsconfig.json" | ||
Then the result is { "b.ts": ["B_unused"], "a.ts": ["A_unused"] } | ||
|
||
# TODO xxx fix | ||
Scenario: Include dynamic import via await - in a function, | ||
Given file "a.ts" is | ||
""" | ||
export default type A = 1; | ||
export type A_unused = 2; | ||
""" | ||
And file "b.ts" is | ||
""" | ||
async function imports() { | ||
const A_imported = await import("./a"); | ||
console.log(A_imported); | ||
} | ||
export const B_unused: A = 0 | ||
""" | ||
When analyzing "tsconfig.json" | ||
Then the result is { "b.ts": ["B_unused"], "a.ts": ["A_unused"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import * as ts from 'typescript'; | ||
|
||
// Parse Comments (that can disable ts-unused-exports) | ||
|
||
export const isNodeDisabledViaComment = ( | ||
node: ts.Node, | ||
file: ts.SourceFile, | ||
): boolean => { | ||
const comments = ts.getLeadingCommentRanges( | ||
file.getFullText(), | ||
node.getFullStart(), | ||
); | ||
|
||
if (comments) { | ||
const commentRange = comments[comments.length - 1]; | ||
const commentText = file | ||
.getFullText() | ||
.substring(commentRange.pos, commentRange.end); | ||
if (commentText === '// ts-unused-exports:disable-next-line') { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import * as ts from 'typescript'; | ||
|
||
export const star = ['*']; | ||
|
||
export interface FromWhat { | ||
from: string; | ||
what: string[]; | ||
} | ||
|
||
export const TRIM_QUOTES = /^['"](.*)['"]$/; | ||
|
||
export const getFromText = (moduleSpecifier: string): string => | ||
moduleSpecifier.replace(TRIM_QUOTES, '$1').replace(/\/index$/, ''); | ||
|
||
export const getFrom = (moduleSpecifier: ts.Expression): string => | ||
getFromText(moduleSpecifier.getText()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import * as ts from 'typescript'; | ||
import { FromWhat, getFromText } from './parser.common'; | ||
|
||
// Parse Dynamic Imports | ||
|
||
export const mayContainDynamicImports = (node: ts.Node): boolean => | ||
node.getText().indexOf('import(') > -1; | ||
|
||
type WithExpression = ts.Node & { | ||
expression: ts.Expression; | ||
}; | ||
|
||
export function isWithExpression(node: ts.Node): node is WithExpression { | ||
const myInterface = node as WithExpression; | ||
return !!myInterface.expression; | ||
} | ||
|
||
type WithArguments = ts.Node & { | ||
arguments: ts.NodeArray<ts.Expression>; | ||
}; | ||
|
||
export function isWithArguments(node: ts.Node): node is WithArguments { | ||
const myInterface = node as WithArguments; | ||
return !!myInterface.arguments; | ||
} | ||
|
||
export const addDynamicImports = ( | ||
node: ts.Node, | ||
addImport: (fw: FromWhat) => void, | ||
): void => { | ||
const addImportsInAnyExpression = (node: ts.Node): void => { | ||
const getArgumentFrom = (node: ts.Node): string | undefined => { | ||
if (isWithArguments(node)) { | ||
return node.arguments[0].getText(); | ||
} | ||
}; | ||
|
||
if (isWithExpression(node)) { | ||
let expr = node; | ||
while (isWithExpression(expr)) { | ||
const newExpr = expr.expression; | ||
|
||
if (newExpr.getText() === 'import') { | ||
const importing = getArgumentFrom(expr); | ||
|
||
if (!!importing) { | ||
addImport({ | ||
from: getFromText(importing), | ||
what: ['default'], | ||
}); | ||
} | ||
} | ||
|
||
if (isWithExpression(newExpr)) { | ||
expr = newExpr; | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
}; | ||
|
||
const recurseIntoChildren = (next: ts.Node): void => { | ||
addImportsInAnyExpression(next); | ||
|
||
next.getChildren().forEach(recurseIntoChildren); | ||
}; | ||
|
||
recurseIntoChildren(node); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as ts from 'typescript'; | ||
|
||
import { LocationInFile } from './types'; | ||
import { FromWhat, star, getFrom } from './parser.common'; | ||
|
||
// Parse Exports | ||
|
||
export const extractExportStatement = ( | ||
decl: ts.ExportDeclaration, | ||
): string[] => { | ||
return decl.exportClause | ||
? decl.exportClause.elements.map(e => (e.name || e.propertyName).text) | ||
: []; | ||
}; | ||
|
||
export const extractExportFromImport = ( | ||
decl: ts.ExportDeclaration, | ||
moduleSpecifier: ts.Expression, | ||
): FromWhat => { | ||
const { exportClause } = decl; | ||
const what = exportClause | ||
? exportClause.elements.map(e => (e.propertyName || e.name).text) | ||
: star; | ||
|
||
return { | ||
from: getFrom(moduleSpecifier), | ||
what, | ||
}; | ||
}; | ||
|
||
export const extractExport = (path: string, node: ts.Node): string => { | ||
switch (node.kind) { | ||
case ts.SyntaxKind.VariableStatement: | ||
return (node as ts.VariableStatement).declarationList.declarations[0].name.getText(); | ||
case ts.SyntaxKind.FunctionDeclaration: | ||
const { name } = node as ts.FunctionDeclaration; | ||
return name ? name.text : 'default'; | ||
default: { | ||
console.warn(`WARN: ${path}: unknown export node (kind:${node.kind})`); | ||
break; | ||
} | ||
} | ||
return ''; | ||
}; | ||
|
||
export const addExportCore = ( | ||
exportName: string, | ||
file: ts.SourceFile, | ||
node: ts.Node, | ||
exportLocations: LocationInFile[], | ||
exports: string[], | ||
): void => { | ||
exports.push(exportName); | ||
|
||
const location = file.getLineAndCharacterOfPosition(node.getStart()); | ||
|
||
exportLocations.push({ | ||
line: location.line + 1, | ||
character: location.character, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { dirname, join, relative, resolve, sep } from 'path'; | ||
import { existsSync } from 'fs'; | ||
import * as tsconfigPaths from 'tsconfig-paths'; | ||
import * as ts from 'typescript'; | ||
|
||
import { getFrom, FromWhat, star } from './parser.common'; | ||
import { Imports } from './types'; | ||
|
||
// Parse Imports | ||
|
||
const EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx']; | ||
|
||
const relativeTo = (rootDir: string, file: string, path: string): string => | ||
relative(rootDir, resolve(dirname(file), path)); | ||
|
||
const isRelativeToBaseDir = (baseDir: string, from: string): boolean => | ||
existsSync(resolve(baseDir, `${from}.js`)) || | ||
existsSync(resolve(baseDir, `${from}.ts`)) || | ||
existsSync(resolve(baseDir, `${from}.tsx`)) || | ||
existsSync(resolve(baseDir, from, 'index.js')) || | ||
existsSync(resolve(baseDir, from, 'index.ts')) || | ||
existsSync(resolve(baseDir, from, 'index.tsx')); | ||
|
||
export const extractImport = (decl: ts.ImportDeclaration): FromWhat => { | ||
const from = getFrom(decl.moduleSpecifier); | ||
const { importClause } = decl; | ||
if (!importClause) | ||
return { | ||
from, | ||
what: star, | ||
}; | ||
|
||
const { namedBindings } = importClause; | ||
const importDefault = !!importClause.name ? ['default'] : []; | ||
const importStar = | ||
namedBindings && !!(namedBindings as ts.NamespaceImport).name ? star : []; | ||
const importNames = | ||
namedBindings && !importStar.length | ||
? (namedBindings as ts.NamedImports).elements.map( | ||
e => (e.propertyName || e.name).text, | ||
) | ||
: []; | ||
|
||
return { | ||
from, | ||
what: importDefault.concat(importStar, importNames), | ||
}; | ||
}; | ||
|
||
export const addImportCore = ( | ||
fw: FromWhat, | ||
rootDir: string, | ||
path: string, | ||
imports: Imports, | ||
tsconfigPathsMatcher?: tsconfigPaths.MatchPath, | ||
baseDir?: string, | ||
baseUrl?: string, | ||
): string | undefined => { | ||
const { from, what } = fw; | ||
|
||
const getKey = (from: string): string | undefined => { | ||
if (from[0] == '.') { | ||
// An undefined return indicates the import is from 'index.ts' or similar == '.' | ||
return relativeTo(rootDir, path, from) || '.'; | ||
} else if (baseDir && baseUrl) { | ||
let matchedPath; | ||
|
||
return isRelativeToBaseDir(baseDir, from) | ||
? baseUrl && join(baseUrl, from) | ||
: tsconfigPathsMatcher && | ||
(matchedPath = tsconfigPathsMatcher( | ||
from, | ||
undefined, | ||
undefined, | ||
EXTENSIONS, | ||
)) | ||
? matchedPath.replace(`${baseDir}${sep}`, '') | ||
: undefined; | ||
} | ||
|
||
return undefined; | ||
}; | ||
|
||
const key = getKey(from); | ||
if (!key) return undefined; | ||
const items = imports[key] || []; | ||
imports[key] = items.concat(what); | ||
return key; | ||
}; |
Oops, something went wrong.