@lit-labs/analyzer
Advanced tools
Comparing version 0.7.0 to 0.8.0
@@ -8,3 +8,2 @@ /** | ||
import * as path from 'path'; | ||
import { DiagnosticsError } from './errors.js'; | ||
import { Analyzer } from './analyzer.js'; | ||
@@ -74,5 +73,4 @@ /** | ||
const analyzer = new Analyzer({ getProgram: () => program, fs: ts.sys, path }); | ||
const diagnostics = program.getSemanticDiagnostics(); | ||
if (diagnostics.length > 0) { | ||
throw new DiagnosticsError(diagnostics, `Error analyzing package '${packagePath}': Please fix errors first`); | ||
for (const diagnostic of program.getSyntacticDiagnostics()) { | ||
analyzer.addDiagnostic(diagnostic); | ||
} | ||
@@ -79,0 +77,0 @@ return analyzer; |
@@ -25,2 +25,3 @@ /** | ||
private _commandLine; | ||
private readonly diagnostics; | ||
constructor(init: AnalyzerInit); | ||
@@ -31,2 +32,4 @@ get program(): ts.Program; | ||
getPackage(): Package; | ||
addDiagnostic(diagnostic: ts.Diagnostic): void; | ||
getDiagnostics(): Generator<ts.Diagnostic, void, undefined>; | ||
} | ||
@@ -33,0 +36,0 @@ /** |
@@ -19,2 +19,3 @@ /** | ||
this._commandLine = undefined; | ||
this.diagnostics = []; | ||
this._getProgram = init.getProgram; | ||
@@ -47,2 +48,8 @@ this.fs = init.fs; | ||
} | ||
addDiagnostic(diagnostic) { | ||
this.diagnostics.push(diagnostic); | ||
} | ||
*getDiagnostics() { | ||
yield* ts.sortAndDeduplicateDiagnostics(this.diagnostics); | ||
} | ||
} | ||
@@ -49,0 +56,0 @@ /** |
@@ -74,4 +74,4 @@ /** | ||
*/ | ||
const addNamedJSDocInfoToMap = (map, tag) => { | ||
const info = parseNamedJSDocInfo(tag); | ||
const addNamedJSDocInfoToMap = (map, tag, analyzer) => { | ||
const info = parseNamedJSDocInfo(tag, analyzer); | ||
if (info !== undefined) { | ||
@@ -98,12 +98,12 @@ map.set(info.name, info); | ||
case 'slot': | ||
addNamedJSDocInfoToMap(slots, tag); | ||
addNamedJSDocInfoToMap(slots, tag, analyzer); | ||
break; | ||
case 'cssProp': | ||
addNamedJSDocInfoToMap(cssProperties, tag); | ||
addNamedJSDocInfoToMap(cssProperties, tag, analyzer); | ||
break; | ||
case 'cssProperty': | ||
addNamedJSDocInfoToMap(cssProperties, tag); | ||
addNamedJSDocInfoToMap(cssProperties, tag, analyzer); | ||
break; | ||
case 'cssPart': | ||
addNamedJSDocInfoToMap(cssParts, tag); | ||
addNamedJSDocInfoToMap(cssParts, tag, analyzer); | ||
break; | ||
@@ -114,3 +114,3 @@ } | ||
return { | ||
...parseNodeJSDocInfo(node), | ||
...parseNodeJSDocInfo(node, analyzer), | ||
events, | ||
@@ -117,0 +117,0 @@ slots, |
@@ -13,3 +13,3 @@ /** | ||
export const addEventsToMap = (tag, events, analyzer) => { | ||
const info = parseNamedTypedJSDocInfo(tag); | ||
const info = parseNamedTypedJSDocInfo(tag, analyzer); | ||
if (info === undefined) { | ||
@@ -16,0 +16,0 @@ return; |
@@ -7,3 +7,10 @@ /** | ||
import ts from 'typescript'; | ||
export declare const createDiagnostic: (node: ts.Node, message: string) => { | ||
import { DiagnosticCode } from './diagnostic-code.js'; | ||
export interface DiagnosticOptions { | ||
node: ts.Node; | ||
message: string; | ||
category?: ts.DiagnosticCategory; | ||
code?: DiagnosticCode | undefined; | ||
} | ||
export declare const createDiagnostic: ({ node, message, category, code, }: DiagnosticOptions) => { | ||
file: ts.SourceFile; | ||
@@ -13,11 +20,5 @@ start: number; | ||
category: ts.DiagnosticCategory; | ||
code: number; | ||
code: DiagnosticCode; | ||
messageText: string; | ||
}; | ||
export declare class DiagnosticsError extends Error { | ||
readonly nodeOrDiagnostics: readonly ts.Diagnostic[] | ts.Node; | ||
diagnostics: ts.Diagnostic[]; | ||
constructor(diagnostics: readonly ts.Diagnostic[], message?: string); | ||
constructor(node: ts.Node, message: string); | ||
} | ||
//# sourceMappingURL=errors.d.ts.map |
@@ -7,38 +7,13 @@ /** | ||
import ts from 'typescript'; | ||
const diagnosticsHost = { | ||
getCanonicalFileName(name) { | ||
return name; | ||
}, | ||
getCurrentDirectory() { | ||
return process.cwd(); | ||
}, | ||
getNewLine() { | ||
return '\n'; | ||
}, | ||
import { DiagnosticCode } from './diagnostic-code.js'; | ||
export const createDiagnostic = ({ node, message, category, code, }) => { | ||
return { | ||
file: node.getSourceFile(), | ||
start: node.getStart(), | ||
length: node.getWidth(), | ||
category: category ?? ts.DiagnosticCategory.Error, | ||
code: code ?? DiagnosticCode.UNKNOWN, | ||
messageText: message ?? '', | ||
}; | ||
}; | ||
export const createDiagnostic = (node, message) => ({ | ||
file: node.getSourceFile(), | ||
start: node.getStart(), | ||
length: node.getWidth(), | ||
category: ts.DiagnosticCategory.Error, | ||
code: 2323, | ||
messageText: message ?? '', | ||
}); | ||
export class DiagnosticsError extends Error { | ||
constructor(nodeOrDiagnostics, message) { | ||
let diagnostics; | ||
if (Array.isArray(nodeOrDiagnostics)) { | ||
diagnostics = nodeOrDiagnostics; | ||
} | ||
else { | ||
const node = nodeOrDiagnostics; | ||
diagnostics = [createDiagnostic(node, message)]; | ||
message = undefined; | ||
} | ||
super((message ? message + ':\n' : '') + | ||
ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticsHost)); | ||
this.nodeOrDiagnostics = nodeOrDiagnostics; | ||
this.diagnostics = diagnostics; | ||
} | ||
} | ||
//# sourceMappingURL=errors.js.map |
@@ -34,3 +34,3 @@ /** | ||
*/ | ||
export declare const getClassDeclarationInfo: (declaration: ts.ClassDeclaration, analyzer: AnalyzerInterface) => DeclarationInfo; | ||
export declare const getClassDeclarationInfo: (declaration: ts.ClassDeclaration, analyzer: AnalyzerInterface) => DeclarationInfo | undefined; | ||
/** | ||
@@ -41,3 +41,3 @@ * Returns the superClass and any applied mixins for a given class declaration. | ||
export declare const getHeritageFromExpression: (expression: ts.Expression, analyzer: AnalyzerInterface) => ClassHeritage; | ||
export declare const getSuperClass: (expression: ts.Expression, analyzer: AnalyzerInterface) => Reference; | ||
export declare const getSuperClass: (expression: ts.Expression, analyzer: AnalyzerInterface) => Reference | undefined; | ||
//# sourceMappingURL=classes.d.ts.map |
@@ -12,3 +12,4 @@ /** | ||
import ts from 'typescript'; | ||
import { DiagnosticsError } from '../errors.js'; | ||
import { DiagnosticCode } from '../diagnostic-code.js'; | ||
import { createDiagnostic } from '../errors.js'; | ||
import { ClassDeclaration, ClassField, ClassMethod, } from '../model.js'; | ||
@@ -41,3 +42,3 @@ import { isLitElementSubclass, getLitElementDeclaration, } from '../lit-element/lit-element.js'; | ||
getHeritage: () => getHeritage(declaration, analyzer), | ||
...parseNodeJSDocInfo(docNode ?? declaration), | ||
...parseNodeJSDocInfo(docNode ?? declaration, analyzer), | ||
...getClassMembers(declaration, analyzer), | ||
@@ -63,6 +64,16 @@ }); | ||
...getFunctionLikeInfo(node, name, analyzer), | ||
...parseNodeJSDocInfo(node), | ||
...parseNodeJSDocInfo(node, analyzer), | ||
})); | ||
} | ||
else if (ts.isPropertyDeclaration(node)) { | ||
if (!ts.isIdentifier(node.name)) { | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node, | ||
message: '@lit-labs/analyzer only supports analyzing class properties ' + | ||
'named with plain identifiers. This property was ignored.', | ||
category: ts.DiagnosticCategory.Warning, | ||
code: DiagnosticCode.UNSUPPORTED, | ||
})); | ||
return; | ||
} | ||
const info = getMemberInfo(node); | ||
@@ -73,3 +84,3 @@ (info.static ? staticFieldMap : fieldMap).set(node.name.getText(), new ClassField({ | ||
type: getTypeForNode(node, analyzer), | ||
...parseNodeJSDocInfo(node), | ||
...parseNodeJSDocInfo(node, analyzer), | ||
})); | ||
@@ -95,3 +106,3 @@ } | ||
*/ | ||
const getClassDeclarationName = (declaration) => { | ||
const getClassDeclarationName = (declaration, analyzer) => { | ||
const name = declaration.name?.text ?? | ||
@@ -102,3 +113,6 @@ // The only time a class declaration will not have a name is when it is | ||
if (name === undefined) { | ||
throw new DiagnosticsError(declaration, 'Unexpected class declaration without a name'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: declaration, | ||
message: `Illegal syntax: a class declaration must either have a name or be a default export`, | ||
})); | ||
} | ||
@@ -111,5 +125,9 @@ return name; | ||
export const getClassDeclarationInfo = (declaration, analyzer) => { | ||
const name = getClassDeclarationName(declaration); | ||
const name = getClassDeclarationName(declaration, analyzer); | ||
if (name === undefined) { | ||
return undefined; | ||
} | ||
return { | ||
name, | ||
node: declaration, | ||
factory: () => getClassDeclaration(declaration, name, analyzer), | ||
@@ -125,6 +143,9 @@ isExport: hasExportModifier(declaration), | ||
if (extendsClause !== undefined) { | ||
if (extendsClause.types.length !== 1) { | ||
throw new DiagnosticsError(extendsClause, 'Internal error: did not expect extends clause to have multiple types'); | ||
if (extendsClause.types.length === 1) { | ||
return getHeritageFromExpression(extendsClause.types[0].expression, analyzer); | ||
} | ||
return getHeritageFromExpression(extendsClause.types[0].expression, analyzer); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: extendsClause, | ||
message: 'Illegal syntax: did not expect extends clause to have multiple types', | ||
})); | ||
} | ||
@@ -152,4 +173,10 @@ // No extends clause; return empty heritage | ||
} | ||
throw new DiagnosticsError(expression, `Expected expression to be a concrete superclass. Mixins are not yet supported.`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: expression, | ||
message: `Expected expression to be a concrete superclass. Mixins are not yet supported.`, | ||
code: DiagnosticCode.UNSUPPORTED, | ||
category: ts.DiagnosticCategory.Warning, | ||
})); | ||
return undefined; | ||
}; | ||
//# sourceMappingURL=classes.js.map |
@@ -13,3 +13,3 @@ /** | ||
import { AnalyzerInterface, DeclarationInfo, FunctionDeclaration, FunctionLikeInit } from '../model.js'; | ||
export declare const getFunctionDeclarationInfo: (declaration: ts.FunctionDeclaration, analyzer: AnalyzerInterface) => DeclarationInfo; | ||
export declare const getFunctionDeclarationInfo: (declaration: ts.FunctionDeclaration, analyzer: AnalyzerInterface) => DeclarationInfo | undefined; | ||
/** | ||
@@ -16,0 +16,0 @@ * Returns an analyzer `FunctionDeclaration` model for the given |
@@ -12,3 +12,3 @@ /** | ||
import ts from 'typescript'; | ||
import { DiagnosticsError } from '../errors.js'; | ||
import { createDiagnostic } from '../errors.js'; | ||
import { FunctionDeclaration, FunctionOverloadDeclaration, } from '../model.js'; | ||
@@ -21,3 +21,3 @@ import { getTypeForNode, getTypeForType } from '../types.js'; | ||
*/ | ||
const getFunctionDeclarationName = (declaration) => { | ||
const getFunctionDeclarationName = (declaration, analyzer) => { | ||
const name = declaration.name?.text ?? | ||
@@ -28,3 +28,6 @@ // The only time a function declaration will not have a name is when it is | ||
if (name === undefined) { | ||
throw new DiagnosticsError(declaration, 'Unexpected function declaration without a name'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: declaration, | ||
message: 'Illegal syntax: expected every function declaration to either have a name or be a default export', | ||
})); | ||
} | ||
@@ -34,5 +37,9 @@ return name; | ||
export const getFunctionDeclarationInfo = (declaration, analyzer) => { | ||
const name = getFunctionDeclarationName(declaration); | ||
const name = getFunctionDeclarationName(declaration, analyzer); | ||
if (name === undefined) { | ||
return undefined; | ||
} | ||
return { | ||
name, | ||
node: declaration, | ||
factory: () => getFunctionDeclaration(declaration, name, analyzer), | ||
@@ -52,3 +59,3 @@ isExport: hasExportModifier(declaration), | ||
return new FunctionDeclaration({ | ||
...parseNodeJSDocInfo(docNode ?? declaration), | ||
...parseNodeJSDocInfo(docNode ?? declaration, analyzer), | ||
...getFunctionLikeInfo(declaration, name, analyzer), | ||
@@ -74,3 +81,3 @@ }); | ||
// const function assignments to be overloaded as of now. | ||
...parseNodeJSDocInfo(overload), | ||
...parseNodeJSDocInfo(overload, analyzer), | ||
// `info` can't be spread because `FunctionLikeInit` has an `overloads` | ||
@@ -96,3 +103,3 @@ // property, even though it's always `undefined` in this case. | ||
type: getTypeForNode(param, analyzer), | ||
...(paramTag ? parseJSDocDescription(paramTag) : {}), | ||
...(paramTag ? parseJSDocDescription(paramTag, analyzer) : {}), | ||
optional: false, | ||
@@ -119,9 +126,14 @@ rest: false, | ||
if (signature === undefined) { | ||
throw new DiagnosticsError(node, `Could not get signature to determine return type`); | ||
// TODO: when does this happen? is it actionable for the user? if so, how? | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node, | ||
message: `Could not get signature to determine return type`, | ||
})); | ||
return undefined; | ||
} | ||
return { | ||
type: getTypeForType(signature.getReturnType(), node, analyzer), | ||
...(returnTag ? parseJSDocDescription(returnTag) : {}), | ||
...(returnTag ? parseJSDocDescription(returnTag, analyzer) : {}), | ||
}; | ||
}; | ||
//# sourceMappingURL=functions.js.map |
@@ -12,3 +12,3 @@ /** | ||
import ts from 'typescript'; | ||
import { Described, TypedNamedDescribed, DeprecatableDescribed, NamedDescribed } from '../model.js'; | ||
import { Described, TypedNamedDescribed, DeprecatableDescribed, AnalyzerInterface, NamedDescribed } from '../model.js'; | ||
/** | ||
@@ -36,3 +36,3 @@ * @fileoverview | ||
*/ | ||
export declare const parseNamedTypedJSDocInfo: (tag: ts.JSDocTag) => TypedNamedDescribed | undefined; | ||
export declare const parseNamedTypedJSDocInfo: (tag: ts.JSDocTag, analyzer: AnalyzerInterface) => TypedNamedDescribed | undefined; | ||
/** | ||
@@ -52,7 +52,7 @@ * Parses name and description from JSDoc tag for things like `@slot`, | ||
*/ | ||
export declare const parseNamedJSDocInfo: (tag: ts.JSDocTag, requireDash?: boolean) => NamedDescribed | undefined; | ||
export declare const parseNamedJSDocInfo: (tag: ts.JSDocTag, analyzer: AnalyzerInterface, requireDash?: boolean) => NamedDescribed | undefined; | ||
/** | ||
* Parses the description from JSDoc tag for things like `@return`. | ||
*/ | ||
export declare const parseJSDocDescription: (tag: ts.JSDocTag) => Described | undefined; | ||
export declare const parseJSDocDescription: (tag: ts.JSDocTag, analyzer: AnalyzerInterface) => Described | undefined; | ||
/** | ||
@@ -62,3 +62,3 @@ * Parse summary, description, and deprecated information from JSDoc comments on | ||
*/ | ||
export declare const parseNodeJSDocInfo: (node: ts.Node) => DeprecatableDescribed; | ||
export declare const parseNodeJSDocInfo: (node: ts.Node, analyzer: AnalyzerInterface) => DeprecatableDescribed; | ||
/** | ||
@@ -68,3 +68,3 @@ * Parse summary, description, and deprecated information from JSDoc comments on | ||
*/ | ||
export declare const parseModuleJSDocInfo: (sourceFile: ts.SourceFile) => DeprecatableDescribed; | ||
export declare const parseModuleJSDocInfo: (sourceFile: ts.SourceFile, analyzer: AnalyzerInterface) => DeprecatableDescribed; | ||
//# sourceMappingURL=jsdoc.d.ts.map |
@@ -12,3 +12,3 @@ /** | ||
import ts from 'typescript'; | ||
import { DiagnosticsError } from '../errors.js'; | ||
import { createDiagnostic } from '../errors.js'; | ||
/** | ||
@@ -38,3 +38,3 @@ * @fileoverview | ||
const parseNameDescRE = /^\[?(?<name>[^[\]\s=]+)(?:=(?<defaultValue>[^\]]+))?\]?(?:\s+-\s+)?(?<description>[\s\S]*)$/; | ||
const getJSDocTagComment = (tag) => { | ||
const getJSDocTagComment = (tag, analyzer) => { | ||
let { comment } = tag; | ||
@@ -48,3 +48,7 @@ if (comment === undefined) { | ||
if (typeof comment !== 'string') { | ||
throw new DiagnosticsError(tag, `Internal error: unsupported node type`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: tag, | ||
message: `JSDoc error: unsupported node type`, | ||
})); | ||
return undefined; | ||
} | ||
@@ -69,4 +73,4 @@ return normalizeLineEndings(comment).trim(); | ||
*/ | ||
export const parseNamedTypedJSDocInfo = (tag) => { | ||
const comment = getJSDocTagComment(tag); | ||
export const parseNamedTypedJSDocInfo = (tag, analyzer) => { | ||
const comment = getJSDocTagComment(tag, analyzer); | ||
if (comment == undefined) { | ||
@@ -77,3 +81,7 @@ return undefined; | ||
if (nameTypeDesc === null) { | ||
throw new DiagnosticsError(tag, 'Unexpected JSDoc format'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: tag, | ||
message: `JSDoc error: unexpected JSDoc format`, | ||
})); | ||
return undefined; | ||
} | ||
@@ -87,7 +95,10 @@ const { name, type, description } = nameTypeDesc.groups; | ||
}; | ||
function makeDashParseError(tag, requireDash) { | ||
return new DiagnosticsError(tag, `Unexpected JSDoc format.${requireDash | ||
? ' Tag must contain a whitespace-separated dash between the name and description, ' + | ||
"i.e. '@slot header - This is the description'" | ||
: ''}`); | ||
function makeDashParseDiagnostic(tag, requireDash) { | ||
return createDiagnostic({ | ||
node: tag, | ||
message: `Unexpected JSDoc format.${requireDash | ||
? ' Tag must contain a whitespace-separated dash between the name and description, ' + | ||
"i.e. '@slot header - This is the description'" | ||
: ''}`, | ||
}); | ||
} | ||
@@ -108,4 +119,4 @@ /** | ||
*/ | ||
export const parseNamedJSDocInfo = (tag, requireDash = false) => { | ||
const comment = getJSDocTagComment(tag); | ||
export const parseNamedJSDocInfo = (tag, analyzer, requireDash = false) => { | ||
const comment = getJSDocTagComment(tag, analyzer); | ||
if (comment == undefined) { | ||
@@ -117,3 +128,4 @@ return undefined; | ||
if (nameDesc === null) { | ||
throw makeDashParseError(tag, requireDash); | ||
analyzer.addDiagnostic(makeDashParseDiagnostic(tag, requireDash)); | ||
return undefined; | ||
} | ||
@@ -133,4 +145,4 @@ const { name, description, defaultValue } = nameDesc.groups; | ||
*/ | ||
export const parseJSDocDescription = (tag) => { | ||
const description = getJSDocTagComment(tag); | ||
export const parseJSDocDescription = (tag, analyzer) => { | ||
const description = getJSDocTagComment(tag, analyzer); | ||
if (description == undefined || description.length === 0) { | ||
@@ -145,5 +157,5 @@ return {}; | ||
*/ | ||
const addJSDocTagInfo = (info, jsDocTags) => { | ||
const addJSDocTagInfo = (info, jsDocTags, analyzer) => { | ||
for (const tag of jsDocTags) { | ||
const comment = getJSDocTagComment(tag); | ||
const comment = getJSDocTagComment(tag, analyzer); | ||
switch (tag.tagName.text.toLowerCase()) { | ||
@@ -222,3 +234,3 @@ case 'description': | ||
*/ | ||
export const parseNodeJSDocInfo = (node) => { | ||
export const parseNodeJSDocInfo = (node, analyzer) => { | ||
const info = {}; | ||
@@ -234,3 +246,3 @@ const moduleJSDocs = getModuleJSDocs(node.getSourceFile()); | ||
if (jsDocTags !== undefined) { | ||
addJSDocTagInfo(info, jsDocTags); | ||
addJSDocTagInfo(info, jsDocTags, analyzer); | ||
} | ||
@@ -255,6 +267,6 @@ if (info.description === undefined) { | ||
*/ | ||
export const parseModuleJSDocInfo = (sourceFile) => { | ||
export const parseModuleJSDocInfo = (sourceFile, analyzer) => { | ||
const moduleJSDocs = getModuleJSDocs(sourceFile); | ||
const info = {}; | ||
addJSDocTagInfo(info, moduleJSDocs.flatMap((m) => m.tags ?? [])); | ||
addJSDocTagInfo(info, moduleJSDocs.flatMap((m) => m.tags ?? []), analyzer); | ||
if (info.description === undefined) { | ||
@@ -261,0 +273,0 @@ const comment = moduleJSDocs |
@@ -29,7 +29,7 @@ /** | ||
*/ | ||
export declare const getPathForModuleSpecifierExpression: (specifierExpression: ts.Expression, analyzer: AnalyzerInterface) => AbsolutePath; | ||
export declare const getPathForModuleSpecifierExpression: (specifierExpression: ts.Expression, analyzer: AnalyzerInterface) => AbsolutePath | undefined; | ||
/** | ||
* Resolve a module specifier to an absolute path on disk. | ||
*/ | ||
export declare const getPathForModuleSpecifier: (specifier: string, location: ts.Node, analyzer: AnalyzerInterface) => AbsolutePath; | ||
export declare const getPathForModuleSpecifier: (specifier: string, location: ts.Node, analyzer: AnalyzerInterface) => AbsolutePath | undefined; | ||
/** | ||
@@ -36,0 +36,0 @@ * Returns the declaration for the named export of the given module path; |
@@ -17,3 +17,3 @@ /** | ||
import { getPackageInfo } from './packages.js'; | ||
import { DiagnosticsError } from '../errors.js'; | ||
import { createDiagnostic } from '../errors.js'; | ||
import { getExportReferences, getImportReferenceForSpecifierExpression, getSpecifierString, } from '../references.js'; | ||
@@ -64,5 +64,10 @@ import { parseModuleJSDocInfo } from './jsdoc.js'; | ||
const addDeclaration = (info) => { | ||
const { name, factory, isExport } = info; | ||
const { name, node, factory, isExport } = info; | ||
if (declarationMap.has(name)) { | ||
throw new Error(`Internal error: duplicate declaration '${name}' in ${sourceFile.fileName}`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node, | ||
message: `Duplicate declaration '${name}'`, | ||
category: ts.DiagnosticCategory.Error, | ||
})); | ||
return; | ||
} | ||
@@ -78,3 +83,6 @@ declarationMap.set(name, factory); | ||
if (ts.isClassDeclaration(statement)) { | ||
addDeclaration(getClassDeclarationInfo(statement, analyzer)); | ||
const decl = getClassDeclarationInfo(statement, analyzer); | ||
if (decl !== undefined) { | ||
addDeclaration(decl); | ||
} | ||
// Ignore non-implementation signatures of overloaded functions by | ||
@@ -84,3 +92,6 @@ // checking for `statement.body`. | ||
else if (ts.isFunctionDeclaration(statement) && statement.body) { | ||
addDeclaration(getFunctionDeclarationInfo(statement, analyzer)); | ||
const decl = getFunctionDeclarationInfo(statement, analyzer); | ||
if (decl !== undefined) { | ||
addDeclaration(decl); | ||
} | ||
} | ||
@@ -101,5 +112,10 @@ else if (ts.isVariableStatement(statement)) { | ||
if (moduleSpecifier === undefined) { | ||
throw new DiagnosticsError(statement, `Expected a wildcard export to have a module specifier.`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: statement, | ||
message: `Unexpected syntax: expected a wildcard export to always have a module specifier.`, | ||
})); | ||
} | ||
reexports.push(moduleSpecifier); | ||
else { | ||
reexports.push(moduleSpecifier); | ||
} | ||
} | ||
@@ -116,3 +132,6 @@ else { | ||
else if (ts.isImportDeclaration(statement)) { | ||
dependencies.add(getPathForModuleSpecifierExpression(statement.moduleSpecifier, analyzer)); | ||
const path = getPathForModuleSpecifierExpression(statement.moduleSpecifier, analyzer); | ||
if (path !== undefined) { | ||
dependencies.add(path); | ||
} | ||
} | ||
@@ -128,3 +147,3 @@ } | ||
finalizeExports: () => finalizeExports(reexports, exportMap, analyzer), | ||
...parseModuleJSDocInfo(sourceFile), | ||
...parseModuleJSDocInfo(sourceFile, analyzer), | ||
}); | ||
@@ -141,3 +160,7 @@ analyzer.moduleCache.set(analyzer.path.normalize(sourceFile.fileName), module); | ||
for (const moduleSpecifier of reexportSpecifiers) { | ||
const module = getModule(getPathForModuleSpecifierExpression(moduleSpecifier, analyzer), analyzer); | ||
const path = getPathForModuleSpecifierExpression(moduleSpecifier, analyzer); | ||
if (path === undefined) { | ||
continue; | ||
} | ||
const module = getModule(path, analyzer); | ||
for (const name of module.exportNames) { | ||
@@ -241,3 +264,8 @@ exportMap.set(name, getImportReferenceForSpecifierExpression(moduleSpecifier, name, analyzer)); | ||
if (resolvedPath === undefined) { | ||
throw new DiagnosticsError(location, `Could not resolve specifier ${specifier} to filesystem path.`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: location, | ||
message: `Could not resolve specifier ${specifier} to filesystem path.`, | ||
category: ts.DiagnosticCategory.Error, | ||
})); | ||
return undefined; | ||
} | ||
@@ -244,0 +272,0 @@ return analyzer.path.normalize(resolvedPath); |
@@ -24,2 +24,3 @@ /** | ||
name: string; | ||
node: ts.EnumDeclaration; | ||
factory: () => VariableDeclaration; | ||
@@ -26,0 +27,0 @@ isExport: boolean; |
@@ -14,3 +14,2 @@ /** | ||
import { hasExportModifier } from '../utils.js'; | ||
import { DiagnosticsError } from '../errors.js'; | ||
import { getTypeForNode } from '../types.js'; | ||
@@ -20,2 +19,4 @@ import { parseNodeJSDocInfo } from './jsdoc.js'; | ||
import { getClassDeclaration } from './classes.js'; | ||
import { createDiagnostic } from '../errors.js'; | ||
import { DiagnosticCode } from '../diagnostic-code.js'; | ||
/** | ||
@@ -46,3 +47,3 @@ * Returns an analyzer `VariableDeclaration` model for the given | ||
type: getTypeForNode(name, analyzer), | ||
...parseNodeJSDocInfo(statement), | ||
...parseNodeJSDocInfo(statement, analyzer), | ||
}); | ||
@@ -71,2 +72,3 @@ }; | ||
name: name.text, | ||
node: name, | ||
factory: () => getVariableDeclaration(statement, dec, name, analyzer), | ||
@@ -88,3 +90,9 @@ isExport, | ||
else { | ||
throw new DiagnosticsError(dec, `Expected declaration name to either be an Identifier or a BindingPattern`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: dec, | ||
message: `Expected declaration name to either be an identifier or a destructuring`, | ||
category: ts.DiagnosticCategory.Warning, | ||
code: DiagnosticCode.UNSUPPORTED, | ||
})); | ||
return []; | ||
} | ||
@@ -98,2 +106,3 @@ }; | ||
name: 'default', | ||
node: exportAssignment, | ||
factory: () => getExportAssignmentVariableDeclaration(exportAssignment, analyzer), | ||
@@ -116,3 +125,3 @@ isExport: true, | ||
type: getTypeForNode(exportAssignment.expression, analyzer), | ||
...parseNodeJSDocInfo(exportAssignment), | ||
...parseNodeJSDocInfo(exportAssignment, analyzer), | ||
}); | ||
@@ -123,2 +132,3 @@ }; | ||
name: dec.name.text, | ||
node: dec, | ||
factory: () => getEnumDeclaration(dec, analyzer), | ||
@@ -133,5 +143,5 @@ isExport: hasExportModifier(dec), | ||
type: getTypeForNode(dec.name, analyzer), | ||
...parseNodeJSDocInfo(dec), | ||
...parseNodeJSDocInfo(dec, analyzer), | ||
}); | ||
}; | ||
//# sourceMappingURL=variables.js.map |
@@ -14,4 +14,5 @@ /** | ||
import { getPropertyDecorator, getPropertyOptions } from './decorators.js'; | ||
import { DiagnosticsError } from '../errors.js'; | ||
import { hasStaticModifier } from '../utils.js'; | ||
import { DiagnosticCode } from '../diagnostic-code.js'; | ||
import { createDiagnostic } from '../errors.js'; | ||
export const getProperties = (classDeclaration, analyzer) => { | ||
@@ -25,3 +26,10 @@ const reactiveProperties = new Map(); | ||
if (!ts.isIdentifier(prop.name)) { | ||
throw new DiagnosticsError(prop, 'Unsupported property name'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: prop, | ||
message: '@lit-labs/analyzer only supports analyzing class properties named with plain identifiers. This ' + | ||
'property was ignored.', | ||
category: ts.DiagnosticCategory.Warning, | ||
code: DiagnosticCode.UNSUPPORTED, | ||
})); | ||
continue; | ||
} | ||
@@ -76,3 +84,6 @@ const name = prop.name.text; | ||
// Find the object literal from the initializer or getter return value | ||
const object = getStaticPropertiesObjectLiteral(properties); | ||
const object = getStaticPropertiesObjectLiteral(properties, analyzer); | ||
if (object === undefined) { | ||
return; | ||
} | ||
// Loop over each key/value in the object and add them to the map | ||
@@ -98,3 +109,8 @@ for (const prop of object.properties) { | ||
else { | ||
throw new DiagnosticsError(prop, 'Unsupported static properties entry. Expected a string identifier key and object literal value.'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: prop, | ||
message: 'Unsupported static properties entry. Expected a string identifier key and object literal value.', | ||
code: DiagnosticCode.UNSUPPORTED, | ||
category: ts.DiagnosticCategory.Warning, | ||
})); | ||
} | ||
@@ -116,3 +132,3 @@ } | ||
*/ | ||
const getStaticPropertiesObjectLiteral = (properties) => { | ||
const getStaticPropertiesObjectLiteral = (properties, analyzer) => { | ||
let object = undefined; | ||
@@ -137,3 +153,8 @@ if (ts.isPropertyDeclaration(properties) && | ||
if (object === undefined) { | ||
throw new DiagnosticsError(properties, `Unsupported static properties format. Expected an object literal assigned in a static initializer or returned from a static getter.`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: properties, | ||
message: `Unsupported static properties format. Expected an object literal assigned in a static initializer or returned from a static getter.`, | ||
code: DiagnosticCode.UNSUPPORTED, | ||
category: ts.DiagnosticCategory.Warning, | ||
})); | ||
} | ||
@@ -140,0 +161,0 @@ return object; |
@@ -458,2 +458,4 @@ /** | ||
path: Pick<typeof import('path'), 'join' | 'relative' | 'dirname' | 'basename' | 'dirname' | 'parse' | 'normalize' | 'isAbsolute'>; | ||
addDiagnostic(diagnostic: ts.Diagnostic): void; | ||
getDiagnostics(): IterableIterator<ts.Diagnostic>; | ||
} | ||
@@ -465,2 +467,3 @@ /** | ||
name: string; | ||
node: ts.Node; | ||
factory: () => Declaration; | ||
@@ -467,0 +470,0 @@ isExport?: boolean; |
@@ -21,3 +21,3 @@ /** | ||
*/ | ||
export declare const getReferenceForIdentifier: (identifier: ts.Identifier, analyzer: AnalyzerInterface) => Reference; | ||
export declare const getReferenceForIdentifier: (identifier: ts.Identifier, analyzer: AnalyzerInterface) => Reference | undefined; | ||
/** | ||
@@ -30,3 +30,3 @@ * Returns an analyzer `Reference` model for the given ts.Symbol. | ||
*/ | ||
export declare function getReferenceForSymbol(symbol: ts.Symbol, location: ts.Node, analyzer: AnalyzerInterface): Reference; | ||
export declare function getReferenceForSymbol(symbol: ts.Symbol, location: ts.Node, analyzer: AnalyzerInterface): Reference | undefined; | ||
/** | ||
@@ -33,0 +33,0 @@ * Returns a `Reference` for a symbol that was imported. |
@@ -7,5 +7,6 @@ /** | ||
import ts from 'typescript'; | ||
import { DiagnosticsError } from './errors.js'; | ||
import { Reference } from './model.js'; | ||
import { getResolvedExportFromSourcePath, getPathForModuleSpecifier, getModuleInfo, } from './javascript/modules.js'; | ||
import { createDiagnostic } from './errors.js'; | ||
import { DiagnosticCode } from './diagnostic-code.js'; | ||
const npmModule = /^(?<package>(@[^/]+\/[^/]+)|[^/]+)\/?(?<module>.*)$/; | ||
@@ -55,3 +56,7 @@ /** | ||
if (symbol === undefined) { | ||
throw new DiagnosticsError(identifier, 'Internal error: Could not get symbol for identifier.'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: identifier, | ||
message: `Could not find symbol for identifier.`, | ||
})); | ||
return undefined; | ||
} | ||
@@ -76,3 +81,7 @@ return getReferenceForSymbol(symbol, identifier, analyzer); | ||
if (declaration === undefined) { | ||
throw new DiagnosticsError(location, `Could not find declaration for symbol '${symbolName}'`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: location, | ||
message: `Could not find declaration for symbol '${symbolName}'`, | ||
})); | ||
return undefined; | ||
} | ||
@@ -165,7 +174,12 @@ const declarationSourceFile = declaration.getSourceFile(); | ||
const info = specifier.match(npmModule); | ||
if (!info || !info.groups) { | ||
throw new DiagnosticsError(location, `External npm package could not be parsed from module specifier '${specifier}'.`); | ||
if (info?.groups) { | ||
refPackage = info.groups.package; | ||
refModule = info.groups.module || undefined; | ||
} | ||
refPackage = info.groups.package; | ||
refModule = info.groups.module || undefined; | ||
else { | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: location, | ||
message: `External npm package could not be parsed from module specifier '${specifier}'.`, | ||
})); | ||
} | ||
} | ||
@@ -177,3 +191,9 @@ } | ||
module: refModule, | ||
dereference: () => getResolvedExportFromSourcePath(getPathForModuleSpecifier(specifier, location, analyzer), name, analyzer), | ||
dereference: () => { | ||
const path = getPathForModuleSpecifier(specifier, location, analyzer); | ||
if (path === undefined) { | ||
return; | ||
} | ||
return getResolvedExportFromSourcePath(path, name, analyzer); | ||
}, | ||
}); | ||
@@ -262,3 +282,7 @@ }; | ||
if (symbol === undefined || decl === undefined) { | ||
throw new DiagnosticsError(el, `Could not find declaration for symbol`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: el, | ||
message: `Could not find declaration for symbol`, | ||
})); | ||
continue; | ||
} | ||
@@ -268,6 +292,9 @@ if (ts.isImportSpecifier(decl)) { | ||
// re-exported, so add a Reference | ||
refs.push({ | ||
exportName, | ||
decNameOrRef: getReferenceForSymbol(symbol, decl, analyzer), | ||
}); | ||
const ref = getReferenceForSymbol(symbol, decl, analyzer); | ||
if (ref !== undefined) { | ||
refs.push({ | ||
exportName, | ||
decNameOrRef: ref, | ||
}); | ||
} | ||
} | ||
@@ -293,3 +320,8 @@ else { | ||
else { | ||
throw new DiagnosticsError(exportClause, `Unhandled form of ExportDeclaration`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: exportClause, | ||
message: `Unhandled form of ExportDeclaration`, | ||
category: ts.DiagnosticCategory.Warning, | ||
code: DiagnosticCode.UNSUPPORTED, | ||
})); | ||
} | ||
@@ -296,0 +328,0 @@ return refs; |
@@ -7,3 +7,2 @@ /** | ||
import ts from 'typescript'; | ||
import { DiagnosticsError } from './errors.js'; | ||
import { getPackageInfo } from './javascript/packages.js'; | ||
@@ -13,2 +12,3 @@ import { Type } from './model.js'; | ||
import { getImportReference, getReferenceForSymbol, getSymbolForName, } from './references.js'; | ||
import { createDiagnostic } from './errors.js'; | ||
/** | ||
@@ -24,3 +24,8 @@ * Returns an analyzer `Type` object for the given type string, | ||
if (typeNode == undefined) { | ||
throw new DiagnosticsError(location, `Internal error: failed to parse type from JSDoc comment.`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: location, | ||
message: `Failed to parse type from JSDoc comment.`, | ||
category: ts.DiagnosticCategory.Warning, | ||
})); | ||
return undefined; | ||
} | ||
@@ -56,9 +61,18 @@ const type = analyzer.program | ||
const typeNode = checker.typeToTypeNode(type, location, ts.NodeBuilderFlags.IgnoreErrors); | ||
let getReferences; | ||
if (typeNode === undefined) { | ||
throw new DiagnosticsError(location, `Internal error: could not convert type to type node`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: location, | ||
message: `Could not convert type to type node`, | ||
category: ts.DiagnosticCategory.Warning, | ||
})); | ||
getReferences = () => []; | ||
} | ||
else { | ||
getReferences = () => getReferencesForTypeNode(typeNode, location, analyzer); | ||
} | ||
return new Type({ | ||
type, | ||
text, | ||
getReferences: () => getReferencesForTypeNode(typeNode, location, analyzer), | ||
getReferences, | ||
}); | ||
@@ -82,13 +96,28 @@ }; | ||
if (symbol === undefined) { | ||
throw new DiagnosticsError(location, `Could not get symbol for '${name}'.`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: location, | ||
message: `Could not get symbol for '${name}'.`, | ||
})); | ||
return; | ||
} | ||
references.push(getReferenceForSymbol(symbol, location, analyzer)); | ||
const ref = getReferenceForSymbol(symbol, location, analyzer); | ||
if (ref !== undefined) { | ||
references.push(ref); | ||
} | ||
} | ||
else if (ts.isImportTypeNode(node)) { | ||
if (!ts.isLiteralTypeNode(node.argument)) { | ||
throw new DiagnosticsError(node, 'Expected a string literal.'); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: node.argument, | ||
message: 'Expected a string literal.', | ||
})); | ||
return; | ||
} | ||
const name = getRootName(node.qualifier); | ||
if (!ts.isStringLiteral(node.argument.literal)) { | ||
throw new DiagnosticsError(location, `Expected import specifier to be a string literal`); | ||
analyzer.addDiagnostic(createDiagnostic({ | ||
node: node.argument.literal, | ||
message: `Expected import specifier to be a string literal`, | ||
})); | ||
return; | ||
} | ||
@@ -95,0 +124,0 @@ const specifier = getSpecifierFromTypeImport(node.argument.literal.text, analyzer); |
{ | ||
"name": "@lit-labs/analyzer", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"publishConfig": { | ||
@@ -5,0 +5,0 @@ "access": "public" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
376919
87
3925