diff --git a/.eslintrc b/.eslintrc index 76cd5cf..48a59f5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,7 @@ "func-style": 0, "quotes": 0, "max-len": [ - "error", + "warn", { "code": 120, "comments": 80, diff --git a/.gitignore b/.gitignore index b512c09..a548411 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules \ No newline at end of file +*.tsbuildinfo +lib/**/*.d.ts +node_modules/ \ No newline at end of file diff --git a/lib/RuleContext.js b/lib/RuleContext.js new file mode 100644 index 0000000..9a0b430 --- /dev/null +++ b/lib/RuleContext.js @@ -0,0 +1,107 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RuleContext = void 0; +const FS = require("fs"); +const Path = require("path"); +const SourceCode_1 = require("./SourceCode"); +const SourceTree_1 = require("./SourceTree"); +/* * + * + * Class + * + * */ +class RuleContext { + /* * + * + * Constructor + * + * */ + constructor(esLintContext, ruleOptionsDefault) { + this.cwd = esLintContext.getCwd(); + this.esLintContext = esLintContext; + this.settings = ((esLintContext.settings || {}).highcharts || {}); + this.options = Object.assign(Object.assign(Object.assign({}, ruleOptionsDefault), this.settings), (esLintContext.options[1] || {})); + this.preparedReports = []; + this.sourcePath = Path.relative(this.cwd, esLintContext.getFilename()); + } + /* * + * + * Static Properties + * + * */ + static setupRuleExport(ruleType, ruleOptionsSchema, ruleOptionsDefault, lintFunction, reportWithFix) { + return { + meta: { + fixable: reportWithFix ? 'code' : void 0, + schema: [{ + additionalProperties: false, + properties: ruleOptionsSchema, + type: 'object' + }], + type: ruleType + }, + create: (esLintRuleContext) => ({ + Program: () => lintFunction(new RuleContext(esLintRuleContext, ruleOptionsDefault)) + }) + }; + } + get sourceCode() { + if (!this._sourceCode) { + const sourcePath = this.sourcePath; + this._sourceCode = new SourceCode_1.default(sourcePath, FS.readFileSync(sourcePath).toString()); + } + return this._sourceCode; + } + get sourceTree() { + if (!this._sourceTree) { + const sourcePath = this.sourcePath; + this._sourceTree = new SourceTree_1.default(sourcePath, FS.readFileSync(sourcePath).toString()); + } + return this._sourceTree; + } + /* * + * + * Functions + * + * */ + prepareReport(position, message) { + this.preparedReports.push({ + loc: { + // ESLint needs column zero-based: + column: position.column - 1, + line: position.line + }, + message + }); + } + sendReports(finalFix) { + const esLintContext = this.esLintContext, reports = this.preparedReports.splice(0); + if (finalFix) { + for (let i = 0, iEnd = reports.length - 1, report; i <= iEnd; ++i) { + report = reports[i]; + if (i === iEnd) { + report.fix = finalFix; + } + else { + report.fix = () => null; + } + esLintContext.report(report); + } + } + else { + for (const report of reports) { + esLintContext.report(report); + } + } + } +} +exports.RuleContext = RuleContext; +/* * + * + * Default Export + * + * */ +exports.default = RuleContext; diff --git a/lib/SourceCode.js b/lib/SourceCode.js new file mode 100644 index 0000000..1db87ea --- /dev/null +++ b/lib/SourceCode.js @@ -0,0 +1,133 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SourceCode = void 0; +const TS = require("typescript"); +const U = require("./Utilities"); +const SourceComment_1 = require("./SourceComment"); +const SourceDoc_1 = require("./SourceDoc"); +const SourceLine_1 = require("./SourceLine"); +/* * + * + * Constant + * + * */ +const tsScanner = TS.createScanner(TS.ScriptTarget.Latest, false); +/* * + * + * Class + * + * */ +class SourceCode { + /* * + * + * Constructor + * + * */ + constructor(fileName, sourceCode) { + this.fileName = fileName; + this.lineBreak = U.detectLineBreak(sourceCode) || '\n'; + this.lines = []; + this.raw = sourceCode; + this.parse(sourceCode); + } + /* * + * + * Functions + * + * */ + getLinePosition(line) { + const lines = this.lines, lineIndex = lines.indexOf(line), position = { + column: 1, + end: 0, + line: 1, + start: 0 + }; + if (lineIndex < 0) { + return null; + } + for (let i = 0, tokens, tokenLength, tokenText; i < lineIndex; ++i) { + tokens = lines[i].tokens; + for (const token of tokens) { + tokenText = token.text; + if (token.kind === TS.SyntaxKind.JSDocComment || + token.kind === TS.SyntaxKind.MultiLineCommentTrivia) { + tokenLength = tokenText.split(U.LINE_BREAKS).length; + if (tokenLength > 1) { + position.line += tokenLength - 1; + } + } + position.start += tokenText.length; + } + position.line += 1; + position.start += 1; // line break + } + position.end = position.start + line.toString().length; + return position; + } + /** + * Returns the token position relative to the code. + */ + getTokenPosition(line, token) { + const linePosition = this.getLinePosition(line), tokenPosition = line.getTokenPosition(token); + if (!linePosition || !tokenPosition) { + return null; + } + return { + column: tokenPosition.column, + end: linePosition.start + tokenPosition.end, + line: linePosition.line + tokenPosition.line - 1, + start: linePosition.start + tokenPosition.start + }; + } + parse(sourceCode, replace = false) { + const lineBreak = this.lineBreak, lines = this.lines; + if (replace) { + lines.length = 0; + } + if (!sourceCode) { + return; + } + let indent, kind, line = new SourceLine_1.default(lineBreak), text, token; + tsScanner.setText(sourceCode); + do { + kind = tsScanner.scan(); + text = tsScanner.getTokenText(); + if (kind === TS.SyntaxKind.NewLineTrivia || + kind === TS.SyntaxKind.EndOfFileToken) { + lines.push(line); + line = new SourceLine_1.default(lineBreak); + continue; + } + if (kind === TS.SyntaxKind.MultiLineCommentTrivia) { + indent = Math.floor(line.getIndent() / 2) * 2; + if (SourceDoc_1.default.isSourceDoc(text)) { + token = new SourceDoc_1.default(text, lineBreak, indent); + } + else { + token = new SourceComment_1.default(text, lineBreak, indent); + } + } + else { + token = { kind, text }; + } + line.tokens.push(token); + } while (kind !== TS.SyntaxKind.EndOfFileToken); + } + toString(maximalLength) { + const lines = this.lines, strings = []; + for (const line of lines) { + strings.push(line.toString(maximalLength)); + } + return strings.join(this.lineBreak); + } +} +exports.SourceCode = SourceCode; +/* * + * + * Default Export + * + * */ +exports.default = SourceCode; diff --git a/lib/SourceComment.js b/lib/SourceComment.js new file mode 100644 index 0000000..e2b2c23 --- /dev/null +++ b/lib/SourceComment.js @@ -0,0 +1,114 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SourceComment = void 0; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const U = require("./Utilities"); +const SourceLine_1 = require("./SourceLine"); +/* * + * + * Constants + * + * */ +const starPattern = /^[ \t]+\*[ \t]?/u; +/* * + * + * Class + * + * */ +class SourceComment extends SourceLine_1.default { + /* * + * + * Constructor + * + * */ + constructor(text, lineBreak = '\n', indent = 0) { + super(lineBreak); + this.indent = indent; + this.kind = TS.SyntaxKind.MultiLineCommentTrivia; + const lines = text.split(U.LINE_BREAKS), tokens = this.tokens; + for (let i = 0, iEnd = lines.length; i < iEnd; ++i) { + tokens.push({ + kind: TS.SyntaxKind.SingleLineCommentTrivia, + text: (i ? lines[i].substr(indent) : lines[i]) + }); + } + } + /* * + * + * Static Functions + * + * */ + static extractCommentLines(text, indent = 0) { + let lines = text.split(U.LINE_BREAKS); + if (lines.length === 1) { + // remove /** and */ + return [text.substr(4, text.length - 7)]; + } + // remove /**\n and \n*/ + lines = lines.slice(1, -1); + for (let i = 0, iEnd = lines.length, line; i < iEnd; ++i) { + line = lines[i]; + if (line.match(starPattern)) { + // remove * + lines[i] = line.replace(starPattern, ''); + } + else if (indent) { + // remove indent + lines[i] = line.substr(indent); + } + } + return lines; + } + get text() { + return this.toString(); + } + /* * + * + * Functions + * + * */ + getIndent() { + return this.indent; + } + toString(maximalLength) { + const indent = this.indent, lines = []; + if (maximalLength) { + let line = '', words; + for (const token of this.tokens) { + words = token.text.split(' '); + line = words.shift() || ''; + for (const word of words) { + if (line.length + 1 + word.length > maximalLength) { + lines.push(line.trimRight()); + line = U.pad(indent, word); + } + else { + line += ` ${word}`; + } + } + lines.push(line.trimRight()); + } + } + else { + for (const token of this.tokens) { + lines.push(U.pad(indent, token.text)); + } + } + return lines.join(this.lineBreak).substr(indent); + } +} +exports.SourceComment = SourceComment; +/* * + * + * Default Export + * + * */ +exports.default = SourceComment; diff --git a/lib/SourceDoc.js b/lib/SourceDoc.js new file mode 100644 index 0000000..36a4eca --- /dev/null +++ b/lib/SourceDoc.js @@ -0,0 +1,201 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SourceDoc = void 0; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const U = require("./Utilities"); +const SourceComment_1 = require("./SourceComment"); +const SourceLine_1 = require("./SourceLine"); +/* * + * + * Constants + * + * */ +// eslint-disable-next-line max-len +const tagPattern = /^@(\S+)([ \t]+\{[^\}\s]+\})?([ \t]+\S+)?(\s[\s\S]*)?$/u; +/* * + * + * Class + * + * */ +class SourceDoc extends SourceLine_1.default { + /* * + * + * Constructor + * + * */ + constructor(text, lineBreak = '\n', indent = 0) { + super(lineBreak); + this.kind = TS.SyntaxKind.JSDocComment; + this.indent = indent; + this.text = text; + this.tokens = []; + const tags = this.tokens, lines = SourceComment_1.default.extractCommentLines(text); + // leading text without tag is @description: + let tag = { + kind: TS.SyntaxKind.JSDocTag, + tagKind: 'description', + text: '' + }; + tags.push(tag); + for (const line of lines) { + if (!line && tags.length > 1) { + tags.push({ + kind: TS.SyntaxKind.NewLineTrivia, + tagKind: '', + text: '' + }); + } + else if (line.startsWith('@')) { + if (tags.length === 1) { + if (!tag.text) { + // remove empty initial description + tags.pop(); + } + else { + // add trailing new lines as tokens + const trail = tag.text.match(new RegExp(`(?:${U.LINE_BREAKS.source})+$`, 'su')) || ['']; + for (let i = 1, iEnd = trail[0].split(U.LINE_BREAKS).length; i < iEnd; ++i) { + tags.push({ + kind: TS.SyntaxKind.NewLineTrivia, + tagKind: '', + text: '' + }); + } + } + } + tag = { + kind: TS.SyntaxKind.JSDocTag, + tagKind: '', + text: line + }; + tags.push(tag); + } + else { + tag.text += lineBreak + line; + } + } + for (const tag of tags) { + SourceDoc.decorateTag(tag); + } + } + /* * + * + * Static Functions + * + * */ + static decorateTag(tag) { + const match = tag.text.match(tagPattern); + if (match) { + const { 1: tagKind, 2: tagType, 3: tagArgument, 4: tagText } = match; + if (tagText) { + tag.text = U.trimBreaks(tagText).trim(); + } + else { + tag.text = ''; + } + if (!tag.tagArgument && tagArgument) { + if (!tagText || tagText.match(/^[\r\n]/u)) { + tag.tagArgument = tagArgument.replace(/^[ \t]/u, ''); + } + else { + tag.text = `${tagArgument} ${tag.text}`.trimLeft(); + } + } + if (!tag.tagType && tagType) { + tag.tagType = tagType.replace(/^[ \t]/u, ''); + } + if (!tag.tagKind && tagKind) { + tag.tagKind = tagKind; + } + } + return tag; + } + static isSourceDoc(text) { + return /^\/\*\*\s/.test(text); + } + /* * + * + * Functions + * + * */ + getIndent() { + return this.indent; + } + toString(maximalLength) { + if (!maximalLength) { + return this.text; + } + const indent = this.indent, tags = this.tokens, firstTag = tags[0], lines = ['/**']; + let part1, part2, padded; + if (firstTag && + firstTag.tagKind === 'description') { + lines.push(U.indent(indent, ' * ', firstTag.text, maximalLength)); + tags.shift(); + } + for (const tag of tags) { + if (tag.tagKind === '') { + lines.push(U.pad(indent, ' *')); + continue; + } + part1 = `@${tag.tagKind}`; + part2 = tag.text; + if (tag.tagKind === 'example') { + lines.push(U.pad(indent, ` * ${part1}`)); + lines.push(U.pad(indent, ` * ${U.trimBreaks(part2)}`)); + continue; + } + if (tag.tagType && tag.tagArgument) { + part1 = `${part1} ${tag.tagType} ${tag.tagArgument}`.trim(); + } + else if (tag.tagType) { + part1 += ` ${tag.tagType}`; + } + else if (tag.tagArgument) { + part1 += ` ${tag.tagArgument}`; + } + if ((!part2 && tag.tagType && tag.tagArgument) || + !(part2 && tag.tagType && tag.tagArgument)) { + padded = U.pad(indent, ` * ${part1} ${part2}`.trimRight()); + // test for one line style + if (padded.length <= maximalLength) { + lines.push(padded); + continue; + } + } + padded = U.pad(indent, ` * ${part1}`); + // test for spaced style + if (padded.length <= maximalLength) { + lines.push(padded); + } + else { + lines.push(U.pad(indent, ` * ${U.trimAll(part1)}`)); + } + if (part2) { + padded = ' * '; + // extra indent for @param etc + if (tag.tagArgument && + tag.tagArgument[0] !== ' ') { + padded += U.pad(tag.tagKind.length + 2); + } + lines.push(U.indent(indent, padded, U.trimAll(part2), maximalLength)); + } + } + lines.push(U.pad(indent, ' */')); + return lines.join(this.lineBreak); + } +} +exports.SourceDoc = SourceDoc; +/* * + * + * Default Export + * + * */ +exports.default = SourceDoc; diff --git a/lib/SourceLine.js b/lib/SourceLine.js new file mode 100644 index 0000000..1e44d8f --- /dev/null +++ b/lib/SourceLine.js @@ -0,0 +1,144 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SourceLine = void 0; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const U = require("./Utilities"); +/* * + * + * Class + * + * */ +class SourceLine { + /* * + * + * Constructor + * + * */ + constructor(lineBreak = '\n') { + this.lineBreak = lineBreak; + this.tokens = []; + } + /* * + * + * Functions + * + * */ + getEssentialTokens() { + const essentials = [], tokens = this.tokens; + for (const token of tokens) { + switch (token.kind) { + case TS.SyntaxKind.EndOfFileToken: + case TS.SyntaxKind.NewLineTrivia: + case TS.SyntaxKind.WhitespaceTrivia: + continue; + default: + essentials.push(token); + } + } + return essentials; + } + getIndent() { + const firstToken = this.tokens[0]; + if (firstToken && + firstToken.kind === TS.SyntaxKind.WhitespaceTrivia) { + return firstToken.text.length; + } + return 0; + } + getMaximalLength() { + const lines = this.getWrappedLines(); + let lineLength, maximalLength = 0; + for (const line of lines) { + lineLength = line.length; + if (lineLength > maximalLength) { + maximalLength = lineLength; + } + } + return maximalLength; + } + getTokenKinds(start, end) { + const tokenKinds = [], tokens = this.tokens, tokensLength = tokens.length; + if (start && start >= tokensLength) { + return []; + } + for (let i = Math.max(start || 0, 0), iEnd = Math.min(end || tokensLength, tokensLength); i < iEnd; ++i) { + tokenKinds.push(tokens[i].kind); + } + return tokenKinds; + } + /** + * Returns the token position relative to the line. + */ + getTokenPosition(token) { + const tokens = this.tokens, tokenIndex = tokens.indexOf(token), position = { + column: 1, + end: 0, + line: 1, + start: 0 + }; + if (tokenIndex < 0) { + return null; + } + for (let i = 0, tokenText; i < tokenIndex; ++i) { + tokenText = tokens[i].text; + if (token.kind === TS.SyntaxKind.JSDocComment || + token.kind === TS.SyntaxKind.MultiLineCommentTrivia) { + position.line += tokenText.split(U.LINE_BREAKS).length - 1; + } + position.start += tokenText.length; + } + position.column = position.start + 1; + position.end = position.start + token.text.length; + return position; + } + getWrappedLines() { + return this.toString().split(U.LINE_BREAKS); + } + toString(maximalLength) { + const lines = [], tokens = this.tokens; + let line = ''; + if (!tokens.length) { + return line; + } + if (maximalLength) { + let tokenText; + for (const token of tokens) { + if (token instanceof SourceLine && + token.tokens.length > 1) { + tokenText = token.toString(maximalLength); + } + else { + tokenText = token.text; + } + if ((U.extractLastLine(line) + U.extractFirstLine(tokenText)).length > maximalLength) { + lines.push(line); + line = ''; + } + line += tokenText; + } + lines.push(line); + } + else { + for (const token of tokens) { + line += token.text; + } + lines.push(line); + } + return lines.join(this.lineBreak); + } +} +exports.SourceLine = SourceLine; +/* * + * + * Default Export + * + * */ +exports.default = SourceLine; diff --git a/lib/SourceNode.js b/lib/SourceNode.js new file mode 100644 index 0000000..bf3d8ea --- /dev/null +++ b/lib/SourceNode.js @@ -0,0 +1,60 @@ +"use strict"; +/* * + * + * Imports + * + * */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SourceNode = void 0; +/* * + * + * Class + * + * */ +class SourceNode { + /* * + * + * Constructor + * + * */ + constructor(kind, text = '') { + this.kind = kind; + this.text = text; + } + /* * + * + * Functions + * + * */ + toArray() { + const children = this.children, parent = new SourceNode(this.kind, this.text), result = [parent]; + parent.doclet = this.doclet; + parent.types = this.types; + if (children) { + for (let i = 0, iEnd = children.length, childrensChildren; i < iEnd; ++i) { + childrensChildren = children[i].toArray(); + for (let j = 0, jEnd = childrensChildren.length; j < jEnd; ++j) { + result.push(childrensChildren[j]); + } + } + } + return result; + } + toString() { + const children = this.children; + let text = this.text; + if (children) { + for (let i = 0, iEnd = children.length; i < iEnd; ++i) { + text += children.toString(); + } + } + return text; + } +} +exports.SourceNode = SourceNode; +/* * + * + * Default Export + * + * */ +exports.default = SourceNode; diff --git a/lib/SourceParser.js b/lib/SourceParser.js new file mode 100644 index 0000000..149b5bf --- /dev/null +++ b/lib/SourceParser.js @@ -0,0 +1,159 @@ +"use strict"; +/* * + * + * Imports + * + * */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseChildren = exports.parse = void 0; +const TS = require("typescript"); +const U = require("./Utilities"); +const SourceNode_1 = require("./SourceNode"); +/* * + * + * Functions + * + * */ +function ignoreChildrenOf(tsNode) { + return (TS.isExpressionStatement(tsNode) || + TS.isImportDeclaration(tsNode) || + TS.isPropertySignature(tsNode) || + TS.isReturnStatement(tsNode) || + TS.isVariableDeclaration(tsNode)); +} +function joinNodeArray(tsSourceFile, tsNodes, separator = ',') { + const nodes = []; + for (const tsNode of tsNodes) { + nodes.push(tsNode.getText(tsSourceFile)); + } + return nodes.join(separator); +} +function parse(tsSourceFile, tsNode) { + let sourceNode; + if (TS.isForStatement(tsNode)) { + sourceNode = parseFor(tsSourceFile, tsNode); + } + else if (TS.isFunctionDeclaration(tsNode)) { + sourceNode = parseFunction(tsSourceFile, tsNode); + } + else if (TS.isIfStatement(tsNode)) { + sourceNode = parseIf(tsSourceFile, tsNode); + } + else if (TS.isInterfaceDeclaration(tsNode)) { + sourceNode = parseInterface(tsSourceFile, tsNode); + } + else if (TS.isModuleDeclaration(tsNode)) { + sourceNode = parseModule(tsSourceFile, tsNode); + } + else if (TS.isVariableStatement(tsNode)) { + sourceNode = parseVariables(tsSourceFile, tsNode); + } + else if (ignoreChildrenOf(tsNode)) { + const types = U.extractTypes(tsSourceFile, tsNode); + sourceNode = new SourceNode_1.default(tsNode.kind, tsNode.getText(tsSourceFile)); + sourceNode.types = types; + } + else { + const tsNodeChildren = tsNode.getChildren(tsSourceFile); + sourceNode = new SourceNode_1.default(tsNode.kind); + if (tsNodeChildren.length) { + sourceNode.children = parseChildren(tsSourceFile, tsNodeChildren); + } + else { + sourceNode.text = tsNode.getText(tsSourceFile); + } + } + if (U.isDocumentedNode(tsNode)) { + const doclet = joinNodeArray(tsSourceFile, tsNode.jsDoc, '\n'); + if (doclet) { + sourceNode.doclet = doclet; + } + } + return sourceNode; +} +exports.parse = parse; +function parseChildren(tsSourceFile, tsNodeChildren) { + const sourceChildren = []; + for (const tsNodeChild of tsNodeChildren) + switch (tsNodeChild.kind) { + case TS.SyntaxKind.CommaToken: + continue; + case TS.SyntaxKind.SyntaxList: + return parseChildren(tsSourceFile, tsNodeChild.getChildren(tsSourceFile)); + default: + sourceChildren.push(parse(tsSourceFile, tsNodeChild)); + continue; + } + return sourceChildren; +} +exports.parseChildren = parseChildren; +function parseFor(tsSourceFile, tsNode) { + const sourceNode = new SourceNode_1.default(tsNode.kind); + sourceNode.children = parseChildren(tsSourceFile, tsNode.statement.getChildren(tsSourceFile)); + if (tsNode.initializer) { + sourceNode.text += `${tsNode.initializer.getText(tsSourceFile)};`; + } + if (tsNode.condition) { + sourceNode.text += `${tsNode.condition.getText(tsSourceFile)};`; + } + if (tsNode.incrementor) { + sourceNode.text += `${tsNode.incrementor.getText(tsSourceFile)};`; + } + return sourceNode; +} +function parseFunction(tsSourceFile, tsNode) { + const sourceNode = new SourceNode_1.default(tsNode.kind, tsNode.name ? tsNode.name.getText(tsSourceFile) : ''); + if (tsNode.body) { + sourceNode.children = parseChildren(tsSourceFile, tsNode.body.getChildren(tsSourceFile)); + } + return sourceNode; +} +function parseIf(tsSourceFile, tsNode) { + const sourceNode = new SourceNode_1.default(tsNode.kind, tsNode.expression.getText(tsSourceFile)); + sourceNode.children = parseChildren(tsSourceFile, tsNode.thenStatement.getChildren(tsSourceFile)); + if (tsNode.elseStatement) { + const tsFirstChild = tsNode.elseStatement.getFirstToken(tsSourceFile); + if (tsFirstChild && + TS.isIfStatement(tsFirstChild) && + tsNode.elseStatement.getChildCount(tsSourceFile) === 1) { + const elseIfSourceNode = parseIf(tsSourceFile, tsFirstChild); + elseIfSourceNode.text = `else ${elseIfSourceNode.text}`; + sourceNode.children.push(elseIfSourceNode); + } + else { + const elseSourceNode = new SourceNode_1.default(tsNode.kind, 'else'); + elseSourceNode.children = parseChildren(tsSourceFile, tsNode.elseStatement.getChildren(tsSourceFile)); + sourceNode.children.push(elseSourceNode); + } + } + return sourceNode; +} +function parseInterface(tsSourceFile, tsNode) { + const sourceNode = new SourceNode_1.default(tsNode.kind, tsNode.name.getText(tsSourceFile)); + sourceNode.children = parseChildren(tsSourceFile, tsNode.members); + if (tsNode.typeParameters) { + sourceNode.text += `<${joinNodeArray(tsSourceFile, tsNode.typeParameters)}>`; + } + if (tsNode.heritageClauses) { + sourceNode.types = [ + joinNodeArray(tsSourceFile, tsNode.heritageClauses) + ]; + } + return sourceNode; +} +function parseModule(tsSourceFile, tsNode) { + const sourceNode = new SourceNode_1.default(tsNode.kind, tsNode.name.getText(tsSourceFile)); + if (tsNode.body) { + sourceNode.children = parseChildren(tsSourceFile, tsNode.body.getChildren(tsSourceFile)); + } + return sourceNode; +} +function parseVariables(tsSourceFile, tsNode) { + const tsFirstChild = tsNode.getFirstToken(tsSourceFile); + if (!tsFirstChild) { + return new SourceNode_1.default(tsNode.kind); + } + const sourceNode = new SourceNode_1.default(tsNode.declarationList.kind, tsFirstChild.getText(tsSourceFile)); + sourceNode.children = parseChildren(tsSourceFile, tsNode.declarationList.getChildren(tsSourceFile)); + return sourceNode; +} diff --git a/lib/SourceTree.js b/lib/SourceTree.js new file mode 100644 index 0000000..0a832ca --- /dev/null +++ b/lib/SourceTree.js @@ -0,0 +1,75 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SourceTree = void 0; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const SP = require("./SourceParser"); +/* * + * + * Class + * + * */ +class SourceTree { + /* * + * + * Constructor + * + * */ + constructor(fileName, sourceCode) { + this.fileName = fileName; + this.nodes = []; + this.parse(sourceCode); + } + /* * + * + * Functions + * + * */ + parse(sourceCode, replace = false) { + const nodes = this.nodes; + if (replace) { + nodes.length = 0; + } + if (!sourceCode) { + return; + } + const tsSourceFile = TS.createSourceFile('', sourceCode, TS.ScriptTarget.Latest), roots = SP.parseChildren(tsSourceFile, tsSourceFile.getChildren()); + if (roots) { + for (let i = 0, iEnd = roots.length; i < iEnd; ++i) { + nodes.push(roots[i]); + } + } + } + toArray() { + const nodes = this.nodes, result = []; + for (let i = 0, iEnd = nodes.length, nodesChildren; i < iEnd; ++i) { + nodesChildren = nodes[i].toArray(); + for (let j = 0, jEnd = nodesChildren.length; j < jEnd; ++j) { + result.push(nodesChildren[j]); + } + } + return result; + } + toString() { + const nodes = this.nodes; + let text = ''; + for (let i = 0, iEnd = nodes.length; i < iEnd; ++i) { + text += nodes[i].toString(); + } + return text; + } +} +exports.SourceTree = SourceTree; +/* * + * + * Default Export + * + * */ +exports.default = SourceTree; diff --git a/lib/Utilities.js b/lib/Utilities.js new file mode 100644 index 0000000..ddf9ceb --- /dev/null +++ b/lib/Utilities.js @@ -0,0 +1,175 @@ +/** + * @author Sophie Bremer + */ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.trimBreaks = exports.trimAll = exports.removeBreaks = exports.pad = exports.isNodeStatement = exports.isNodeClass = exports.isExpressionNode = exports.isDocumentedNode = exports.indent = exports.detectLineBreak = exports.extractLastLine = exports.extractFirstLine = exports.extractTypes = exports.SPACES = exports.PARAGRAPHS = exports.LINE_BREAKS = void 0; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +/* * + * + * Constants + * + * */ +exports.LINE_BREAKS = /\r\n|\r|\n/gu; +exports.PARAGRAPHS = new RegExp(`(?:${exports.LINE_BREAKS.source}){2,2}`, 'gu'); +exports.SPACES = /[ \t]/gu; +/* * + * + * Functions + * + * */ +function extractTypes(tsSourceFile, tsNode) { + const tsChildren = tsNode.getChildren(tsSourceFile), types = []; + for (const tsChild of tsChildren) { + if (TS.isTypeReferenceNode(tsChild)) { + types.push(tsChild.getText(tsSourceFile)); + } + } + return types; +} +exports.extractTypes = extractTypes; +function extractFirstLine(text) { + return text.split(exports.LINE_BREAKS)[0]; +} +exports.extractFirstLine = extractFirstLine; +function extractLastLine(text) { + const lines = text.split(exports.LINE_BREAKS); + return lines[lines.length - 1]; +} +exports.extractLastLine = extractLastLine; +function detectLineBreak(text) { + var _a; + return (_a = text.match(new RegExp(exports.LINE_BREAKS.source, 'u'))) === null || _a === void 0 ? void 0 : _a[0]; +} +exports.detectLineBreak = detectLineBreak; +/** + * Returns a indented string, that fits into a specific width and spans over + * several lines. + * + * @param text + * The string to pad. + * + * @param indent + * The prefix for each line. + * + * @param wrap + * The maximum width of the padded string. + */ +function indent(indent, prefix, text, wrap) { + const lb = detectLineBreak(text) || '\n'; + prefix = pad(indent, prefix); + if (!wrap) { + return prefix + text.replace(exports.LINE_BREAKS, `${lb}${prefix}`); + } + const fragments = text + .replace(exports.PARAGRAPHS, ' \x00 ') // paragraphs + .replace(exports.LINE_BREAKS, ' \x05 ') // single break + .trim() + .split(exports.SPACES); + let codeBlock = false, newLine = true, line = prefix, newParagraph = false, paddedStr = ''; + for (const fragment of fragments) { + if (fragment === '\x00') { + newLine = true; + newParagraph = true; + paddedStr += line.trimRight() + lb + prefix.trimRight() + lb; + continue; + } + if (fragment === '\x05') { + if (codeBlock) { + newLine = true; + paddedStr += line.trimRight() + lb; + } + else if (newParagraph) { + newLine = true; + paddedStr += prefix.trimRight() + lb; + } + continue; + } + if (fragment.startsWith('```')) { + codeBlock = !codeBlock; + if (!newLine) { + newLine = true; + paddedStr += line.trimRight() + lb; + } + } + if (!codeBlock && + !newLine && + line.trimRight().length + 1 + fragment.length > wrap) { + newLine = true; + paddedStr += line.trimRight() + lb; + } + if (newLine) { + newLine = false; + line = prefix + fragment; + } + else { + line += ' ' + fragment; + } + if (fragment && newParagraph) { + newParagraph = false; + } + } + return newLine ? paddedStr : paddedStr + line.trimRight(); +} +exports.indent = indent; +function isDocumentedNode(node) { + return (typeof node.jsDoc === 'object'); +} +exports.isDocumentedNode = isDocumentedNode; +function isExpressionNode(node) { + return (typeof node.expression === 'object'); +} +exports.isExpressionNode = isExpressionNode; +function isNodeClass(node, nodeClass) { + const kindClass = TS.SyntaxKind[node.kind]; + return !!kindClass && kindClass.endsWith(nodeClass); +} +exports.isNodeClass = isNodeClass; +function isNodeStatement(node) { + return (TS.isBreakOrContinueStatement(node) || + TS.isDebuggerStatement(node) || + TS.isDoStatement(node) || + TS.isEmptyStatement(node) || + TS.isExpressionStatement(node) || + TS.isForInStatement(node) || + TS.isForOfStatement(node) || + TS.isForStatement(node) || + TS.isIfStatement(node) || + TS.isLabeledStatement(node) || + TS.isReturnStatement(node) || + TS.isSwitchStatement(node) || + TS.isThrowStatement(node) || + TS.isTryStatement(node) || + TS.isVariableStatement(node) || + TS.isWhileStatement(node)); +} +exports.isNodeStatement = isNodeStatement; +function pad(indent, suffix = '') { + return ' '.repeat(indent) + suffix; +} +exports.pad = pad; +function removeBreaks(text) { + return text.replace(exports.LINE_BREAKS, ' ').trim(); +} +exports.removeBreaks = removeBreaks; +function trimAll(text, keepParagraphs = false) { + const lb = detectLineBreak(text) || '\n'; + if (keepParagraphs) { + return text + .replace(exports.PARAGRAPHS, ' \x00 ') + .replace(/\s+/gu, ' ') + .trim() + .replace(/ ?\x00 ?/, `${lb}${lb}`); + } + return text.replace(/\s+/gu, ' ').trim(); +} +exports.trimAll = trimAll; +function trimBreaks(text) { + return text.replace(new RegExp(`^(?:${exports.LINE_BREAKS.source}){1,}|(?:${exports.LINE_BREAKS.source}){1,}$`, 'gu'), ''); +} +exports.trimBreaks = trimBreaks; diff --git a/lib/rules/debug.js b/lib/rules/debug.js new file mode 100644 index 0000000..ec6a688 --- /dev/null +++ b/lib/rules/debug.js @@ -0,0 +1,38 @@ +/** + * @fileoverview Debugs TypeScript tokens. + * @author Sophie Bremer + */ +'use strict'; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const optionsDefaults = {}; +const optionsSchema = { + 'onlyKindOf': { + 'type': 'array' + } +}; +/* * + * + * Functions + * + * */ +function lint(context) { + const onlyKindOf = context.options.onlyKindOf, sourceTree = context.sourceTree, sourceNodes = sourceTree.toArray(); + for (const sourceNode of sourceNodes) { + if (onlyKindOf && !onlyKindOf.includes(sourceNode.kind)) { + continue; + } + console.log(sourceNode.kind, TS.SyntaxKind[sourceNode.kind], sourceNode.text, sourceNode.types, sourceNode.doclet); + } +} +module.exports = RuleContext_1.default.setupRuleExport('layout', optionsSchema, optionsDefaults, lint); diff --git a/lib/rules/generic-array-type.js b/lib/rules/generic-array-type.js new file mode 100644 index 0000000..e2375c4 --- /dev/null +++ b/lib/rules/generic-array-type.js @@ -0,0 +1,97 @@ +/** + * @fileoverview Array types should always be written in generic syntax to avoid + * any confusion with array assignments, array indexer, or type selectors. + * @author Sophie Bremer + */ +'use strict'; +const TS = require("typescript"); +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const message = [ + 'Do not use the [] shortcut for the array type.', + 'Instead write the generic Array<...> to improve readability.' +].join(' '); +const optionsDefaults = {}; +const optionsSchema = {}; +/* * + * + * Functions + * + * */ +function createFixer(context, linesToFix) { + return () => { + const code = context.sourceCode, fix = [], range = [0, code.raw.length + 256], lines = code.lines; + let firstToken, secondToken, thirdToken, tokenReplacements, tokens; + for (const l of lines) { + if (linesToFix.includes(l)) { + tokens = l.tokens; + for (let i = 0, iEnd = tokens.length - 2; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i + 1]; + thirdToken = tokens[i + 2]; + if (isMatch(firstToken, secondToken, thirdToken)) { + tokenReplacements = [{ + kind: TS.SyntaxKind.Identifier, + text: 'Array' + }, { + kind: TS.SyntaxKind.LessThanToken, + text: '<' + }, + firstToken, + { + kind: TS.SyntaxKind.GreaterThanToken, + text: '>' + }]; + tokens.splice(i, 3, ...tokenReplacements); + iEnd = tokens.length - 2; + } + } + } + fix.push(l.toString()); + } + return { range, text: fix.join(code.lineBreak) }; + }; +} +function isMatch(firstToken, secondToken, thirdToken) { + return (secondToken.kind === TS.SyntaxKind.OpenBracketToken && + thirdToken.kind === TS.SyntaxKind.CloseBracketToken && + (firstToken.kind === TS.SyntaxKind.AnyKeyword || + firstToken.kind === TS.SyntaxKind.BooleanKeyword || + firstToken.kind === TS.SyntaxKind.BigIntKeyword || + firstToken.kind === TS.SyntaxKind.CloseBracketToken || + firstToken.kind === TS.SyntaxKind.CloseParenToken || + firstToken.kind === TS.SyntaxKind.GreaterThanToken || + firstToken.kind === TS.SyntaxKind.NullKeyword || + firstToken.kind === TS.SyntaxKind.NumberKeyword || + firstToken.kind === TS.SyntaxKind.ObjectKeyword || + firstToken.kind === TS.SyntaxKind.StringKeyword || + firstToken.kind === TS.SyntaxKind.SymbolKeyword || + firstToken.kind === TS.SyntaxKind.UndefinedKeyword || + firstToken.kind === TS.SyntaxKind.UnknownKeyword || + firstToken.kind === TS.SyntaxKind.VoidKeyword)); +} +function lint(context) { + const code = context.sourceCode, lines = code.lines, linesToFix = []; + let firstToken, secondToken, thirdToken, tokens; + for (const line of lines) { + tokens = line.tokens; + for (let i = 0, iEnd = tokens.length - 2; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i + 1]; + thirdToken = tokens[i + 2]; + if (isMatch(firstToken, secondToken, thirdToken)) { + const position = code.getTokenPosition(line, secondToken); + if (position) { + context.prepareReport(position, message); + linesToFix.push(line); + } + } + } + } + context.sendReports(createFixer(context, linesToFix)); +} +module.exports = RuleContext_1.default.setupRuleExport('layout', optionsSchema, optionsDefaults, lint, true); diff --git a/lib/rules/no-import-effects.js b/lib/rules/no-import-effects.js new file mode 100644 index 0000000..b6d0d8a --- /dev/null +++ b/lib/rules/no-import-effects.js @@ -0,0 +1,90 @@ +/** + * @fileoverview Imports should not be anonymous. Move desired side effects into + * compose functions and call these instead. + * @author Sophie Bremer + */ +'use strict'; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const messageTemplate = [ + 'Imports should not be anonymous.', + 'Create and call a composer for side effects.' +].join(' '); +const optionsDefaults = {}; +const optionsSchema = {}; +/* * + * + * Functions + * + * */ +function lint(context) { + const code = context.sourceCode, importsToCheck = [], tokensToCheck = []; + let firstToken, secondToken, tokens; + for (const line of code.lines) { + if (line.getIndent() !== 0) { + continue; + } + tokens = line.getEssentialTokens(); + for (let i = 0, iEnd = tokens.length; i < iEnd; ++i) { + firstToken = tokens[i]; + if (firstToken.kind === TS.SyntaxKind.DeleteKeyword) { + const identifierIndex = line + .getTokenKinds(i) + .indexOf(TS.SyntaxKind.Identifier) + i; + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } + else if (firstToken.kind === TS.SyntaxKind.EqualsToken) { + const identifierIndex = line + .getTokenKinds(0, i) + .indexOf(TS.SyntaxKind.Identifier); + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } + secondToken = tokens[i + 1]; + if (!secondToken) { + continue; + } + if (firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.Identifier) { + importsToCheck.push(secondToken.text); + } + else if (firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.OpenBraceToken) { + let previousToken; + for (const thisToken of tokens.slice(i + 2)) { + if (previousToken && + previousToken.kind === TS.SyntaxKind.Identifier && + (thisToken.kind === TS.SyntaxKind.CommaToken || + thisToken.kind === TS.SyntaxKind.CloseBraceToken || + thisToken.kind === TS.SyntaxKind.CloseBracketToken)) { + importsToCheck.push(previousToken.text); + } + previousToken = thisToken; + } + } + } + } + for (const token of tokensToCheck) { + if (importsToCheck.includes(token[1].text)) { + const position = code.getTokenPosition(token[0], token[1]); + if (position) { + context.prepareReport(position, messageTemplate); + } + } + } + context.sendReports(); +} +module.exports = RuleContext_1.default.setupRuleExport('problem', optionsSchema, optionsDefaults, lint, false); diff --git a/lib/rules/no-import-modification.js b/lib/rules/no-import-modification.js new file mode 100644 index 0000000..f04c67f --- /dev/null +++ b/lib/rules/no-import-modification.js @@ -0,0 +1,90 @@ +/** + * @fileoverview Imports should not be immediately modified as this would + * prevent async import. Provide a composer to allow modifications by consumers. + * @author Sophie Bremer + */ +'use strict'; +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const messageTemplate = [ + 'Imports should not be immediately modified.', + 'Create a composer.' +].join(' '); +const optionsDefaults = {}; +const optionsSchema = {}; +/* * + * + * Functions + * + * */ +function lint(context) { + const code = context.sourceCode, importsToCheck = [], tokensToCheck = []; + let firstToken, secondToken, tokens; + for (const line of code.lines) { + if (line.getIndent() !== 0) { + continue; + } + tokens = line.getEssentialTokens(); + for (let i = 0, iEnd = tokens.length; i < iEnd; ++i) { + firstToken = tokens[i]; + if (firstToken.kind === TS.SyntaxKind.DeleteKeyword) { + const identifierIndex = line + .getTokenKinds(i) + .indexOf(TS.SyntaxKind.Identifier) + i; + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } + else if (firstToken.kind === TS.SyntaxKind.EqualsToken) { + const identifierIndex = line + .getTokenKinds(0, i) + .indexOf(TS.SyntaxKind.Identifier); + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } + secondToken = tokens[i + 1]; + if (!secondToken) { + continue; + } + if (firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.Identifier) { + importsToCheck.push(secondToken.text); + } + else if (firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.OpenBraceToken) { + let previousToken; + for (const thisToken of tokens.slice(i + 2)) { + if (previousToken && + previousToken.kind === TS.SyntaxKind.Identifier && + (thisToken.kind === TS.SyntaxKind.CommaToken || + thisToken.kind === TS.SyntaxKind.CloseBraceToken || + thisToken.kind === TS.SyntaxKind.CloseBracketToken)) { + importsToCheck.push(previousToken.text); + } + previousToken = thisToken; + } + } + } + } + for (const token of tokensToCheck) { + if (importsToCheck.includes(token[1].text)) { + const position = code.getTokenPosition(token[0], token[1]); + if (position) { + context.prepareReport(position, messageTemplate); + } + } + } + context.sendReports(); +} +module.exports = RuleContext_1.default.setupRuleExport('problem', optionsSchema, optionsDefaults, lint, false); diff --git a/lib/rules/no-import-type.js b/lib/rules/no-import-type.js new file mode 100644 index 0000000..efa8b84 --- /dev/null +++ b/lib/rules/no-import-type.js @@ -0,0 +1,69 @@ +/** + * @fileoverview Explicitly type imports are not necessary because TypeScript + * will automatically remove type-only used imports during transpilation. + * @author Sophie Bremer + */ +'use strict'; +const TS = require("typescript"); +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const message = [ + 'Explicitly type imports are not necessary.', + 'Instead write the generic Array<...> to improve readability.' +].join(' '); +const optionsDefaults = {}; +const optionsSchema = {}; +/* * + * + * Functions + * + * */ +function createFixer(context, linesToFix) { + return () => { + const code = context.sourceCode, fix = [], range = [0, code.raw.length + 256], lines = code.lines; + let firstToken, secondToken, thirdToken, tokens; + for (const l of lines) { + if (linesToFix.includes(l)) { + tokens = l.tokens; + for (let i = 0, iEnd = tokens.length - 2; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i + 1]; + thirdToken = tokens[i + 2]; + if (firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.WhitespaceTrivia && + thirdToken.kind === TS.SyntaxKind.TypeKeyword) { + tokens.splice(i + 1, 2); + iEnd = tokens.length - 2; + } + } + } + fix.push(l.toString()); + } + return { range, text: fix.join(code.lineBreak) }; + }; +} +function lint(context) { + const code = context.sourceCode, lines = code.lines, linesToFix = []; + let firstToken, secondToken, tokens; + for (const line of lines) { + tokens = line.getEssentialTokens(); + for (let i = 0, iEnd = tokens.length - 1; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i + 1]; + if (firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.TypeKeyword) { + const firstPosition = code.getTokenPosition(line, firstToken), secondPosition = code.getTokenPosition(line, secondToken); + if (firstPosition && secondPosition) { + context.prepareReport(secondPosition, message); + linesToFix.push(line); + } + } + } + } + context.sendReports(createFixer(context, linesToFix)); +} +module.exports = RuleContext_1.default.setupRuleExport('layout', optionsSchema, optionsDefaults, lint, true); diff --git a/lib/rules/no-optional-chaining.js b/lib/rules/no-optional-chaining.js index bab9581..0c70edc 100644 --- a/lib/rules/no-optional-chaining.js +++ b/lib/rules/no-optional-chaining.js @@ -1,66 +1,47 @@ -/* - * @fileoverview The optional chaining should not be used, as it bloats - * transpiled ES5 code. Instead make use of the `pick` function or make inline - * chain with `&&` or `||`. +/** + * @fileoverview Do not use optional chaining because it bloats transpiled code. + * Instead use the `pick` function or `&&` conditions. + * @author Sophie Bremer */ 'use strict'; - -const message = 'Do not use optional chaining.'; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -module.exports = { - meta: { - docs: { - description: message, - category: 'Migration', - recommended: false - }, - fixable: null, // or "code" or "whitespace" - schema: [ - // fill in your schema - ] - }, - - create: function (context) { - - // variables should be defined here - const code = context.getSourceCode().lines.join('\n'); - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - const program = (node) => { - - const pattern = /\?\.[\w\[\(]/g; - - let match; - - while (match = pattern.exec(code)) { - const codeBefore = code.substr(0, pattern.lastIndex - match[0].length); - const linesBefore = codeBefore.split('\n'); - const line = linesBefore[linesBefore.length - 1]; - - context.report({ - node: node, - loc: { - line: linesBefore.length, - column: line.length - }, - message: message - }); +/* * + * + * Imports + * + * */ +const TS = require("typescript"); +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const message = [ + 'Do not use optional chaining.', + 'Instead use the `pick` function or `&&` conditions.' +].join(' '); +const optionsDefaults = {}; +const optionsSchema = {}; +/* * + * + * Functions + * + * */ +function lint(context) { + const code = context.sourceCode, lines = code.lines; + let tokens; + for (const line of lines) { + tokens = line.tokens; + for (let index = 0, indexEnd = tokens.length - 2, firstToken; index < indexEnd; ++index) { + firstToken = tokens[index]; + if (firstToken.kind === TS.SyntaxKind.QuestionDotToken) { + const position = code.getTokenPosition(line, firstToken); + if (position) { + context.prepareReport(position, message); + } } - }; - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - return { - - Program: program - - }; + } } -}; + context.sendReports(); +} +module.exports = RuleContext_1.default.setupRuleExport('problem', optionsSchema, optionsDefaults, lint, false); diff --git a/lib/rules/pretty-length.js b/lib/rules/pretty-length.js new file mode 100644 index 0000000..de3f2e7 --- /dev/null +++ b/lib/rules/pretty-length.js @@ -0,0 +1,83 @@ +/** + * @fileoverview Limit and autofix max length. + * @author Sophie Bremer + */ +'use strict'; +const RuleContext_1 = require("../RuleContext"); +/* * + * + * Constants + * + * */ +const messageTemplate = 'Line exceeds limit of {0} characters.'; +const optionsDefaults = { + maximalLength: 80 +}; +const optionsSchema = { + 'ignorePattern': { + 'type': 'string' + }, + 'maximalLength': { + 'type': 'integer' + } +}; +/* * + * + * Functions + * + * */ +function createFixer(context, fixLines) { + return () => { + const code = context.sourceCode, fix = [], range = [0, code.raw.length + 256], lines = code.lines, { ignorePattern, maximalLength } = context.options, ignoreRegExp = (ignorePattern ? new RegExp(ignorePattern) : void 0); + let text; + for (const l of lines) { + text = l.toString(); + if (fixLines.includes(l) && + (!ignoreRegExp || + !ignoreRegExp.test(text))) { + fix.push(l.toString(maximalLength)); + } + else { + fix.push(text); + } + } + return { range, text: fix.join(code.lineBreak) }; + }; +} +function lint(context) { + const code = context.sourceCode, { ignorePattern, maximalLength } = context.options, ignoreRegExp = (ignorePattern ? new RegExp(ignorePattern) : void 0), lines = code.lines, fixLines = [], message = messageTemplate.replace('{0}', `${maximalLength}`); + let maximalLineLength; + for (const line of lines) { + if (ignoreRegExp && + ignoreRegExp.test(line.toString())) { + continue; + } + maximalLineLength = line.getMaximalLength(); + if (maximalLineLength > maximalLength) { + const position = code.getLinePosition(line); + if (position) { + const wrappedLines = line.getWrappedLines(); + if (wrappedLines.length === 1) { + // only lines with multiline comments for now + continue; + } + let lineIndex = 0; + for (const wrappedLine of wrappedLines) { + if (wrappedLine.length > maximalLength && + wrappedLine.split(/\s+/g).length > 1) { + context.prepareReport({ + column: maximalLength + 1, + end: position.end, + line: position.line + lineIndex, + start: position.start + }, message + ` ${maximalLineLength}`); + fixLines.push(line); + } + ++lineIndex; + } + } + } + } + context.sendReports(createFixer(context, fixLines)); +} +module.exports = RuleContext_1.default.setupRuleExport('layout', optionsSchema, optionsDefaults, lint, true); diff --git a/package-lock.json b/package-lock.json index 7ac4f25..516311e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,36 @@ { "name": "@highcharts/eslint-plugin-highcharts", - "version": "1.1.1", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@highcharts/eslint-plugin-highcharts", - "version": "1.1.1", + "version": "2.0.0", "license": "ISC", "dependencies": { + "eslint": "^9.23.0", "requireindex": "~1.2.0" }, "devDependencies": { - "eslint": "^9.23.0", - "mocha": "^11.1.0" + "@types/eslint": "7.28.2", + "@types/json-schema": "7.0.9", + "@types/node": "12.20.36", + "@typescript-eslint/eslint-plugin": "^8.28.0", + "@typescript-eslint/parser": "^8.28.0", + "husky": "7.0.4", + "mocha": "^11.1.0", + "typescript": "^5.8.2" }, "engines": { - "node": ">=22.0.0" + "node": ">=22.0.0", + "typescript": ">=5.0.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", - "dev": true, + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -42,7 +49,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -55,17 +61,15 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", - "dev": true, + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -77,20 +81,21 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", - "dev": true, + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", - "dev": true, + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -99,11 +104,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/core/node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@eslint/eslintrc": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -124,33 +134,33 @@ } }, "node_modules/@eslint/js": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", - "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", - "dev": true, + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", - "dev": true, + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { @@ -161,7 +171,6 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -171,7 +180,6 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -185,7 +193,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -199,7 +206,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -213,7 +219,6 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -241,6 +246,44 @@ "node": ">=12" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -252,26 +295,248 @@ "node": ">=14" } }, + "node_modules/@types/eslint": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz", + "integrity": "sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true, "license": "MIT" }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "node_modules/@types/node": { + "version": "12.20.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.36.tgz", + "integrity": "sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -283,7 +548,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -293,7 +557,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -360,14 +623,12 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/binary-extensions": { @@ -384,10 +645,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -417,7 +677,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -439,7 +698,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -455,7 +713,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -470,7 +727,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -479,7 +735,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -613,7 +868,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -626,21 +880,18 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -655,7 +906,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -685,7 +935,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, "license": "MIT" }, "node_modules/diff": { @@ -726,7 +975,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -735,20 +983,19 @@ } }, "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", - "dev": true, + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -759,9 +1006,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -796,10 +1043,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -813,10 +1059,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -825,11 +1070,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -839,15 +1089,14 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -860,7 +1109,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -873,7 +1121,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -886,7 +1133,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -896,7 +1142,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -906,28 +1151,51 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, "license": "MIT" }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -953,7 +1221,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -978,7 +1245,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -992,7 +1258,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, "license": "ISC" }, "node_modules/foreground-child": { @@ -1071,9 +1336,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1100,7 +1365,6 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1109,6 +1373,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1118,11 +1389,26 @@ "he": "bin/he" } }, + "node_modules/husky": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", + "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -1132,7 +1418,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -1149,7 +1434,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -1171,7 +1455,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1190,7 +1473,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -1233,8 +1515,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "node_modules/jackspeak": { "version": "3.4.3", @@ -1256,7 +1537,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -1269,27 +1549,23 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -1299,7 +1575,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -1313,7 +1588,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -1327,8 +1601,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -1353,11 +1626,34 @@ "dev": true, "license": "ISC" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -1413,9 +1709,9 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1463,14 +1759,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -1486,7 +1780,6 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -1504,7 +1797,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -1519,7 +1811,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -1541,7 +1832,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -1554,7 +1844,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -1563,7 +1852,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -1602,7 +1890,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -1612,12 +1899,32 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1663,12 +1970,46 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1690,6 +2031,19 @@ ], "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -1704,7 +2058,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -1716,7 +2069,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -1842,7 +2194,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -1863,11 +2214,23 @@ "node": ">=8.0" } }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -1876,11 +2239,24 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -1890,7 +2266,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -1905,7 +2280,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2119,7 +2493,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index abbfeb5..cb61939 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@highcharts/eslint-plugin-highcharts", - "version": "1.1.1", + "license": "ISC", + "version": "2.0.0", "description": "ESLint rules for the Highcharts project", "keywords": [ "eslint", @@ -14,17 +15,33 @@ }, "main": "lib/index.js", "scripts": { - "test": "mocha tests --recursive" + "build": "tsc -b sources", + "build-watch": "npm run build -- --watch", + "lint": "eslint sources", + "test": "mocha tests --recursive", + "test-sources": "tsc -b tests/sources && node tests/sources/index.js" }, "dependencies": { + "eslint": "^9.23.0", "requireindex": "~1.2.0" }, "devDependencies": { - "eslint": "^9.23.0", - "mocha": "^11.1.0" + "@types/eslint": "7.28.2", + "@types/json-schema": "7.0.9", + "@types/node": "12.20.36", + "@typescript-eslint/eslint-plugin": "^8.28.0", + "@typescript-eslint/parser": "^8.28.0", + "husky": "7.0.4", + "mocha": "^11.1.0", + "typescript": "^5.8.2" }, "engines": { - "node": ">=22.0.0" + "node": ">=22.0.0", + "typescript": ">=5.0.0" }, - "license": "ISC" + "husky": { + "hooks": { + "pre-commit": "npm run lint && npm run build && npm test" + } + } } diff --git a/sources/.eslintrc b/sources/.eslintrc new file mode 100644 index 0000000..6bafc36 --- /dev/null +++ b/sources/.eslintrc @@ -0,0 +1,12 @@ +{ + "extends": [ + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "rules": { + "@typescript-eslint/no-inferrable-types": 0, + "@typescript-eslint/no-namespace": 0, + "@typescript-eslint/pretty-length": 0 + } +} diff --git a/sources/HighchartsSettings.d.ts b/sources/HighchartsSettings.d.ts new file mode 100644 index 0000000..19e45c3 --- /dev/null +++ b/sources/HighchartsSettings.d.ts @@ -0,0 +1,21 @@ +/* * + * + * Declarations + * + * */ + + +export interface HighchartsSetttings { + indentSize: number; + indentWithTabs: boolean; +} + + +/* * + * + * Default Export + * + * */ + + +export default HighchartsSettings; diff --git a/sources/LintFunction.d.ts b/sources/LintFunction.d.ts new file mode 100644 index 0000000..3061cad --- /dev/null +++ b/sources/LintFunction.d.ts @@ -0,0 +1,36 @@ +/** + * @author Sophie Bremer + */ + + +/* * + * + * Imports + * + * */ + + +import type RuleContext from './RuleContext'; +import type RuleOptions from './RuleOptions'; + + +/* * + * + * Declarations + * + * */ + + +export interface LintFunction { + (ruleContext: RuleContext): void; +} + + +/* * + * + * Default Export + * + * */ + + +export default LintFunction; diff --git a/sources/RuleContext.ts b/sources/RuleContext.ts new file mode 100644 index 0000000..38ab83f --- /dev/null +++ b/sources/RuleContext.ts @@ -0,0 +1,218 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import type * as ESLint from 'eslint'; +import type * as JSONSchema from 'json-schema'; +import type LintFunction from './LintFunction'; +import type RuleOptions from './RuleOptions'; + +import * as FS from 'fs'; +import * as Path from 'path'; +import RuleType from './RuleType'; +import SourceCode from './SourceCode'; +import SourcePosition from './SourcePosition'; +import SourceTree from './SourceTree'; + + +/* * + * + * Class + * + * */ + + +export class RuleContext { + + + /* * + * + * Static Properties + * + * */ + + + public static setupRuleExport ( + ruleType: RuleType, + ruleOptionsSchema: JSONSchema.JSONSchema4, + ruleOptionsDefault: T, + lintFunction: LintFunction, + reportWithFix?: boolean + ): ESLint.Rule.RuleModule { + return { + meta: { + fixable: reportWithFix ? 'code' : void 0, + schema: [{ + additionalProperties: false, + properties: ruleOptionsSchema, + type: 'object' + }], + type: ruleType + }, + create: (esLintRuleContext: ESLint.Rule.RuleContext) => ( + { + Program: () => lintFunction(new RuleContext( + esLintRuleContext, + ruleOptionsDefault + )) + } + ) + }; + } + + + /* * + * + * Constructor + * + * */ + + + private constructor ( + esLintContext: ESLint.Rule.RuleContext, + ruleOptionsDefault: T + ) { + this.cwd = esLintContext.getCwd(); + this.esLintContext = esLintContext; + this.settings = ((esLintContext.settings || {}).highcharts || {}); + this.options = { + ...ruleOptionsDefault, + ...this.settings, + ...(esLintContext.options[1] || {}) + }; + this.preparedReports = []; + this.sourcePath = Path.relative(this.cwd, esLintContext.getFilename()); + } + + + /* * + * + * Properties + * + * */ + + + private _sourceCode?: SourceCode; + + + private _sourceTree?: SourceTree; + + + private cwd: string; + + + private esLintContext: ESLint.Rule.RuleContext; + + + public options: T; + + + public readonly preparedReports: Array; + + + public settings: ESLint.Rule.RuleContext['settings']; + + + public get sourceCode (): SourceCode { + + if (!this._sourceCode) { + const sourcePath = this.sourcePath; + + this._sourceCode = new SourceCode( + sourcePath, + FS.readFileSync(sourcePath).toString() + ); + } + + return this._sourceCode; + } + + + public sourcePath: string; + + + public get sourceTree (): SourceTree { + + if (!this._sourceTree) { + const sourcePath = this.sourcePath; + + this._sourceTree = new SourceTree( + sourcePath, + FS.readFileSync(sourcePath).toString() + ); + } + + return this._sourceTree; + } + + + /* * + * + * Functions + * + * */ + + + public prepareReport( + position: SourcePosition, + message: string + ): void { + this.preparedReports.push({ + loc: { + // ESLint needs column zero-based: + column: position.column - 1, + line: position.line + }, + message + }); + } + + + public sendReports( + finalFix?: ESLint.Rule.ReportFixer + ): void { + const esLintContext = this.esLintContext, + reports = this.preparedReports.splice(0); + + if (finalFix) { + for (let i = 0, iEnd = reports.length - 1, report: ESLint.Rule.ReportDescriptor; i <= iEnd; ++i) { + report = reports[i]; + + if (i === iEnd) { + report.fix = finalFix; + } else { + report.fix = () => null; + } + + esLintContext.report(report); + } + } else { + for (const report of reports) { + esLintContext.report(report); + } + } + } + + +} + + +/* * + * + * Default Export + * + * */ + + +export default RuleContext; diff --git a/sources/RuleOptions.d.ts b/sources/RuleOptions.d.ts new file mode 100644 index 0000000..24d070b --- /dev/null +++ b/sources/RuleOptions.d.ts @@ -0,0 +1,23 @@ +/** + * @author Sophie Bremer + */ + + +/* * + * + * Declarations + * + * */ + + +export type RuleOptions = Record; + + +/* * + * + * Default Export + * + * */ + + +export default RuleOptions; \ No newline at end of file diff --git a/sources/RuleType.d.ts b/sources/RuleType.d.ts new file mode 100644 index 0000000..cd41ae9 --- /dev/null +++ b/sources/RuleType.d.ts @@ -0,0 +1,27 @@ +/** + * @author Sophie Bremer + */ + + +/* * + * + * Declarations + * + * */ + + +export type RuleType = ( + 'layout' | + 'problem' | + 'suggestion' +); + + +/* * + * + * Default Export + * + * */ + + +export default RuleType; diff --git a/sources/SourceCode.ts b/sources/SourceCode.ts new file mode 100644 index 0000000..3893e7a --- /dev/null +++ b/sources/SourceCode.ts @@ -0,0 +1,246 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as Timers from 'timers'; +import * as TS from 'typescript'; +import * as U from './Utilities'; +import SourceComment from './SourceComment'; +import SourceDoc from './SourceDoc'; +import SourceLine from './SourceLine'; +import SourcePosition from './SourcePosition'; +import SourceToken from './SourceToken'; + + +/* * + * + * Constant + * + * */ + + +const tsScanner = TS.createScanner(TS.ScriptTarget.Latest, false); + + +/* * + * + * Class + * + * */ + + +export class SourceCode { + + + /* * + * + * Constructor + * + * */ + + + public constructor ( + fileName: string, + sourceCode: string + ) { + this.fileName = fileName; + this.lineBreak = U.detectLineBreak(sourceCode) || '\n'; + this.lines = []; + this.raw = sourceCode; + this.parse(sourceCode); + } + + + /* * + * + * Properties + * + * */ + + + public readonly fileName: string; + + + public readonly lineBreak: string; + + + public readonly lines: Array; + + + public readonly raw: string; + + + /* * + * + * Functions + * + * */ + + + public getLinePosition( + line: SourceLine + ): (SourcePosition|null) { + const lines = this.lines, + lineIndex = lines.indexOf(line), + position: SourcePosition = { + column: 1, + end: 0, + line: 1, + start: 0 + }; + + if (lineIndex < 0) { + return null; + } + + for ( + let i = 0, + tokens: Array, + tokenLength: number, + tokenText: string; + i < lineIndex; + ++i + ) { + tokens = lines[i].tokens; + + for (const token of tokens) { + tokenText = token.text; + + if ( + token.kind === TS.SyntaxKind.JSDocComment || + token.kind === TS.SyntaxKind.MultiLineCommentTrivia + ) { + tokenLength = tokenText.split(U.LINE_BREAKS).length; + + if (tokenLength > 1) { + position.line += tokenLength - 1; + } + } + + position.start += tokenText.length; + } + + position.line += 1; + position.start += 1; // line break + } + + position.end = position.start + line.toString().length; + + return position; + } + + + /** + * Returns the token position relative to the code. + */ + public getTokenPosition( + line: SourceLine, + token: SourceToken + ): (SourcePosition|null) { + + const linePosition = this.getLinePosition(line), + tokenPosition = line.getTokenPosition(token); + + if (!linePosition || !tokenPosition) { + return null; + } + + return { + column: tokenPosition.column, + end: linePosition.start + tokenPosition.end, + line: linePosition.line + tokenPosition.line - 1, + start: linePosition.start + tokenPosition.start + }; + } + + + public parse ( + sourceCode: string, + replace = false + ) { + const lineBreak = this.lineBreak, + lines = this.lines; + + if (replace) { + lines.length = 0; + } + + if (!sourceCode) { + return; + } + + let indent: number, + kind: TS.SyntaxKind, + line = new SourceLine(lineBreak), + text: string, + token: SourceToken; + + tsScanner.setText(sourceCode); + + do { + kind = tsScanner.scan(); + text = tsScanner.getTokenText(); + + if ( + kind === TS.SyntaxKind.NewLineTrivia || + kind === TS.SyntaxKind.EndOfFileToken + ) { + lines.push(line); + line = new SourceLine(lineBreak); + continue; + } + + if (kind === TS.SyntaxKind.MultiLineCommentTrivia) { + indent = Math.floor(line.getIndent() / 2) * 2; + + if (SourceDoc.isSourceDoc(text)) { + token = new SourceDoc(text, lineBreak, indent); + } else { + token = new SourceComment(text, lineBreak, indent); + } + } else { + token = { kind, text }; + } + + line.tokens.push(token); + + } while (kind !== TS.SyntaxKind.EndOfFileToken); + } + + + public toString ( + maximalLength?: number + ): string { + const lines = this.lines, + strings: Array = []; + + for (const line of lines) { + strings.push(line.toString(maximalLength)); + } + + return strings.join(this.lineBreak); + } + + +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceCode; diff --git a/sources/SourceComment.ts b/sources/SourceComment.ts new file mode 100644 index 0000000..84a7ab4 --- /dev/null +++ b/sources/SourceComment.ts @@ -0,0 +1,186 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import * as U from './Utilities'; +import SourceLine from './SourceLine'; +import SourceToken from './SourceToken'; + + +/* * + * + * Constants + * + * */ + + +const starPattern = /^[ \t]+\*[ \t]?/u + + +/* * + * + * Class + * + * */ + + +export class SourceComment extends SourceLine implements SourceToken { + + + /* * + * + * Static Functions + * + * */ + + + public static extractCommentLines( + text: string, + indent: number = 0 + ): Array { + let lines = text.split(U.LINE_BREAKS); + + if (lines.length === 1) { + // remove /** and */ + return [ text.substr(4, text.length - 7) ]; + } + + // remove /**\n and \n*/ + lines = lines.slice(1, -1); + + for (let i = 0, iEnd = lines.length, line: string; i < iEnd; ++i) { + line = lines[i]; + + if (line.match(starPattern)) { + // remove * + lines[i] = line.replace(starPattern, ''); + } else if (indent) { + // remove indent + lines[i] = line.substr(indent); + } + } + + return lines; + } + + + /* * + * + * Constructor + * + * */ + + + public constructor ( + text: string, + lineBreak: string = '\n', + indent: number = 0 + ) { + super(lineBreak); + + this.indent = indent; + this.kind = TS.SyntaxKind.MultiLineCommentTrivia; + + const lines = text.split(U.LINE_BREAKS), + tokens = this.tokens; + + for (let i = 0, iEnd = lines.length; i < iEnd; ++i) { + tokens.push({ + kind: TS.SyntaxKind.SingleLineCommentTrivia, + text: (i ? lines[i].substr(indent) : lines[i]) + }); + } + } + + + /* * + * + * Properties + * + * */ + + + private readonly indent: number; + + + public readonly kind: TS.SyntaxKind.MultiLineCommentTrivia; + + + public get text (): string { + return this.toString(); + } + + + /* * + * + * Functions + * + * */ + + + public getIndent(): number { + return this.indent; + } + + + public toString( + maximalLength?: number + ): string { + const indent = this.indent, + lines: Array = []; + + if (maximalLength) { + let line: string = '', + words: Array; + + for (const token of this.tokens) { + + words = token.text.split(' '); + line = words.shift() || ''; + + for (const word of words) { + + if (line.length + 1 + word.length > maximalLength) { + lines.push(line.trimRight()); + + line = U.pad(indent, word); + } else { + line += ` ${word}`; + } + } + + lines.push(line.trimRight()); + } + } else { + for (const token of this.tokens) { + lines.push(U.pad(indent, token.text)); + } + } + + return lines.join(this.lineBreak).substr(indent); + } + + +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceComment; diff --git a/sources/SourceDoc.ts b/sources/SourceDoc.ts new file mode 100644 index 0000000..b5faac7 --- /dev/null +++ b/sources/SourceDoc.ts @@ -0,0 +1,319 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import * as U from './Utilities'; +import SourceComment from './SourceComment'; +import SourceDocTag from './SourceDocTag'; +import SourceLine from './SourceLine'; +import SourceToken from './SourceToken'; + + +/* * + * + * Constants + * + * */ + + +// eslint-disable-next-line max-len +const tagPattern = /^@(\S+)([ \t]+\{[^\}\s]+\})?([ \t]+\S+)?(\s[\s\S]*)?$/u; + + +/* * + * + * Class + * + * */ + + +export class SourceDoc extends SourceLine implements SourceToken { + + + /* * + * + * Static Functions + * + * */ + + + private static decorateTag( + tag: SourceDocTag + ): SourceDocTag { + + const match = tag.text.match(tagPattern); + + if (match) { + const { + 1: tagKind, + 2: tagType, + 3: tagArgument, + 4: tagText + } = match; + + if (tagText) { + tag.text = U.trimBreaks(tagText).trim(); + } else { + tag.text = ''; + } + + if (!tag.tagArgument && tagArgument) { + if (!tagText || tagText.match(/^[\r\n]/u)) { + tag.tagArgument = tagArgument.replace(/^[ \t]/u, ''); + } else { + tag.text = `${tagArgument} ${tag.text}`.trimLeft() + } + } + + if (!tag.tagType && tagType) { + tag.tagType = tagType.replace(/^[ \t]/u, ''); + } + + if (!tag.tagKind && tagKind) { + tag.tagKind = tagKind; + } + } + + return tag; + } + + + public static isSourceDoc( + text: string + ): boolean { + return /^\/\*\*\s/.test(text); + } + + + /* * + * + * Constructor + * + * */ + + + public constructor ( + text: string, + lineBreak: string = '\n', + indent: number = 0 + ) { + super(lineBreak); + + this.kind = TS.SyntaxKind.JSDocComment; + this.indent = indent; + this.text = text; + this.tokens = []; + + const tags = this.tokens, + lines = SourceComment.extractCommentLines(text); + + // leading text without tag is @description: + let tag: SourceDocTag = { + kind: TS.SyntaxKind.JSDocTag, + tagKind: 'description', + text: '' + }; + + tags.push(tag); + + for (const line of lines) { + if (!line && tags.length > 1) { + tags.push({ + kind: TS.SyntaxKind.NewLineTrivia, + tagKind: '', + text: '' + }); + } else if (line.startsWith('@')) { + + if (tags.length === 1) { + if (!tag.text) { + // remove empty initial description + tags.pop(); + } else { + // add trailing new lines as tokens + const trail = tag.text.match(new RegExp( + `(?:${U.LINE_BREAKS.source})+$`, + 'su' + )) || ['']; + + for ( + let i = 1, + iEnd = trail[0].split(U.LINE_BREAKS).length; + i < iEnd; + ++i + ) { + tags.push({ + kind: TS.SyntaxKind.NewLineTrivia, + tagKind: '', + text: '' + }); + } + } + } + + tag = { + kind: TS.SyntaxKind.JSDocTag, + tagKind: '', + text: line + }; + + tags.push(tag); + } else { + tag.text += lineBreak + line; + } + } + + for (const tag of tags) { + SourceDoc.decorateTag(tag); + } + } + + + /* * + * + * Properties + * + * */ + + + public readonly kind: TS.SyntaxKind.JSDocComment; + + + private readonly indent: number; + + + public readonly text: string; + + + public readonly tokens: Array; + + + /* * + * + * Functions + * + * */ + + + public getIndent(): number { + return this.indent; + } + + + public toString( + maximalLength?: number + ): string { + + if (!maximalLength) { + return this.text; + } + + const indent = this.indent, + tags = this.tokens, + firstTag = tags[0], + lines: Array = ['/**']; + + let part1: string, + part2: string, + padded: string; + + if ( + firstTag && + firstTag.tagKind === 'description' + ) { + lines.push(U.indent(indent, ' * ', firstTag.text, maximalLength)); + tags.shift(); + } + + for (const tag of tags) { + + if (tag.tagKind === '') { + lines.push(U.pad(indent, ' *')); + continue; + } + + part1 = `@${tag.tagKind}`; + part2 = tag.text; + + if (tag.tagKind === 'example') { + lines.push(U.pad(indent, ` * ${part1}`)); + lines.push(U.pad(indent, ` * ${U.trimBreaks(part2)}`)); + continue; + } + + if (tag.tagType && tag.tagArgument) { + part1 = `${part1} ${tag.tagType} ${tag.tagArgument}`.trim(); + } else if (tag.tagType) { + part1 += ` ${tag.tagType}`; + } else if (tag.tagArgument) { + part1 += ` ${tag.tagArgument}`; + } + + if ( + (!part2 && tag.tagType && tag.tagArgument) || + !(part2 && tag.tagType && tag.tagArgument) + ) { + padded = U.pad(indent, ` * ${part1} ${part2}`.trimRight()); + + // test for one line style + if (padded.length <= maximalLength) { + lines.push(padded); + continue; + } + } + + padded = U.pad(indent, ` * ${part1}`); + + // test for spaced style + if (padded.length <= maximalLength) { + lines.push(padded); + } else { + lines.push(U.pad(indent, ` * ${U.trimAll(part1)}`)); + } + + if (part2) { + padded = ' * '; + + // extra indent for @param etc + if ( + tag.tagArgument && + tag.tagArgument[0] !== ' ' + ) { + padded += U.pad(tag.tagKind.length + 2); + } + + lines.push( + U.indent(indent, padded, U.trimAll(part2), maximalLength) + ); + } + } + + lines.push(U.pad(indent, ' */')); + + return lines.join(this.lineBreak); + } + + +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceDoc; diff --git a/sources/SourceDocTag.d.ts b/sources/SourceDocTag.d.ts new file mode 100644 index 0000000..7789a7d --- /dev/null +++ b/sources/SourceDocTag.d.ts @@ -0,0 +1,33 @@ +/* * + * + * Imports + * + * */ + + +import type * as U from './Utilities'; +import type SourceToken from './SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +export interface SourceDocTag extends SourceToken { + tagArgument?: string; + tagKind: string; + tagType?: string; +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceDocTag; diff --git a/sources/SourceLine.ts b/sources/SourceLine.ts new file mode 100644 index 0000000..e7ce98d --- /dev/null +++ b/sources/SourceLine.ts @@ -0,0 +1,241 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import * as U from './Utilities'; +import SourcePosition from './SourcePosition'; +import SourceToken from './SourceToken'; + + +/* * + * + * Class + * + * */ + + +export class SourceLine { + + + /* * + * + * Constructor + * + * */ + + + public constructor( + lineBreak: string = '\n' + ) { + this.lineBreak = lineBreak; + this.tokens = []; + } + + + /* * + * + * Properties + * + * */ + + + public readonly lineBreak: string; + + + public readonly tokens: Array; + + + /* * + * + * Functions + * + * */ + + + public getEssentialTokens(): Array { + const essentials: Array = [], + tokens = this.tokens; + + for (const token of tokens) { + switch (token.kind) { + case TS.SyntaxKind.EndOfFileToken: + case TS.SyntaxKind.NewLineTrivia: + case TS.SyntaxKind.WhitespaceTrivia: + continue; + default: + essentials.push(token); + } + } + + return essentials; + } + + + public getIndent(): number { + const firstToken = this.tokens[0]; + + if ( + firstToken && + firstToken.kind === TS.SyntaxKind.WhitespaceTrivia + ) { + return firstToken.text.length; + } + + return 0; + } + + + public getMaximalLength(): number { + const lines = this.getWrappedLines(); + + let lineLength: number, + maximalLength = 0; + + for (const line of lines) { + lineLength = line.length; + if (lineLength > maximalLength) { + maximalLength = lineLength; + } + } + + return maximalLength; + } + + + public getTokenKinds(start?: number, end?: number): Array { + const tokenKinds: Array = [], + tokens = this.tokens, + tokensLength = tokens.length; + + if (start && start >= tokensLength) { + return []; + } + + for ( + let i = Math.max(start || 0, 0), + iEnd = Math.min(end || tokensLength, tokensLength); + i < iEnd; + ++i + ) { + tokenKinds.push(tokens[i].kind); + } + + return tokenKinds; + } + + + /** + * Returns the token position relative to the line. + */ + public getTokenPosition( + token: SourceToken + ): (SourcePosition|null) { + const tokens = this.tokens, + tokenIndex = tokens.indexOf(token), + position = { + column: 1, + end: 0, + line: 1, + start: 0 + }; + + if (tokenIndex < 0) { + return null; + } + + for (let i = 0, tokenText: string; i < tokenIndex; ++i) { + tokenText = tokens[i].text; + + if ( + token.kind === TS.SyntaxKind.JSDocComment || + token.kind === TS.SyntaxKind.MultiLineCommentTrivia + ) { + position.line += tokenText.split(U.LINE_BREAKS).length - 1; + } + + position.start += tokenText.length; + } + + position.column = position.start + 1; + position.end = position.start + token.text.length; + + return position; + } + + + public getWrappedLines(): Array { + return this.toString().split(U.LINE_BREAKS); + } + + + public toString( + maximalLength?: number + ): string { + const lines: Array = [], + tokens = this.tokens; + + let line: string = ''; + + if (!tokens.length) { + return line; + } + + if (maximalLength) { + let tokenText: string; + + for (const token of tokens) { + + if ( + token instanceof SourceLine && + token.tokens.length > 1 + ) { + tokenText = token.toString(maximalLength); + } else { + tokenText = token.text; + } + + if ((U.extractLastLine(line) + U.extractFirstLine(tokenText)).length > maximalLength) { + lines.push(line); + line = ''; + } + + line += tokenText; + } + + lines.push(line); + } else { + + for (const token of tokens) { + line += token.text; + } + + lines.push(line); + } + + return lines.join(this.lineBreak); + } + + +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceLine; diff --git a/sources/SourceNode.ts b/sources/SourceNode.ts new file mode 100644 index 0000000..ef9f770 --- /dev/null +++ b/sources/SourceNode.ts @@ -0,0 +1,123 @@ +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import SourceToken from './SourceToken'; + + +/* * + * + * Class + * + * */ + + +export class SourceNode implements SourceToken { + + + /* * + * + * Constructor + * + * */ + + public constructor( + kind: TS.SyntaxKind, + text: string = '' + ) { + this.kind = kind; + this.text = text; + } + + /* * + * + * Properties + * + * */ + + + public children?: Array; + + + public doclet?: string; + + + public kind: TS.SyntaxKind; + + + public text: string; + + + public types?: Array; + + + /* * + * + * Functions + * + * */ + + + public toArray(): Array { + const children = this.children, + parent = new SourceNode(this.kind, this.text), + result: Array = [parent]; + + parent.doclet = this.doclet; + parent.types = this.types; + + if (children) { + for ( + let i = 0, + iEnd = children.length, + childrensChildren: Array; + i < iEnd; + ++i + ) { + childrensChildren = children[i].toArray(); + + for ( + let j = 0, + jEnd = childrensChildren.length; + j < jEnd; + ++j + ) { + result.push(childrensChildren[j]); + } + } + } + + return result; + } + + + public toString(): string { + const children = this.children; + + let text = this.text; + + if (children) { + for (let i = 0, iEnd = children.length; i < iEnd; ++i) { + text += children.toString(); + } + } + + return text; + } + + +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceNode; diff --git a/sources/SourceParser.ts b/sources/SourceParser.ts new file mode 100644 index 0000000..5bc2e56 --- /dev/null +++ b/sources/SourceParser.ts @@ -0,0 +1,282 @@ +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import * as U from './Utilities'; +import SourceNode from './SourceNode'; + + +/* * + * + * Functions + * + * */ + + +function ignoreChildrenOf( + tsNode: TS.Node +): boolean { + return ( + TS.isExpressionStatement(tsNode) || + TS.isImportDeclaration(tsNode) || + TS.isPropertySignature(tsNode) || + TS.isReturnStatement(tsNode) || + TS.isVariableDeclaration(tsNode) + ); +} + + +function joinNodeArray( + tsSourceFile: TS.SourceFile, + tsNodes: ( + Array| + TS.NodeArray + ), + separator: string = ',' +): string { + const nodes: Array = []; + + for (const tsNode of tsNodes) { + nodes.push(tsNode.getText(tsSourceFile)); + } + + return nodes.join(separator); +} + + +export function parse( + tsSourceFile: TS.SourceFile, + tsNode: TS.Node +): SourceNode { + let sourceNode: SourceNode; + + if (TS.isForStatement(tsNode)) { + sourceNode = parseFor(tsSourceFile, tsNode); + } else if (TS.isFunctionDeclaration(tsNode)) { + sourceNode = parseFunction(tsSourceFile, tsNode); + } else if (TS.isIfStatement(tsNode)) { + sourceNode = parseIf(tsSourceFile, tsNode); + } else if (TS.isInterfaceDeclaration(tsNode)) { + sourceNode = parseInterface(tsSourceFile, tsNode); + } else if (TS.isModuleDeclaration(tsNode)) { + sourceNode = parseModule(tsSourceFile, tsNode); + } else if (TS.isVariableStatement(tsNode)) { + sourceNode = parseVariables(tsSourceFile, tsNode); + } else if (ignoreChildrenOf(tsNode)) { + const types = U.extractTypes(tsSourceFile, tsNode); + + sourceNode = new SourceNode( + tsNode.kind, + tsNode.getText(tsSourceFile) + ); + + sourceNode.types = types; + } else { + const tsNodeChildren = tsNode.getChildren(tsSourceFile); + sourceNode = new SourceNode(tsNode.kind); + + if (tsNodeChildren.length) { + sourceNode.children = parseChildren(tsSourceFile, tsNodeChildren); + } else { + sourceNode.text = tsNode.getText(tsSourceFile); + } + } + + if (U.isDocumentedNode(tsNode)) { + const doclet = joinNodeArray(tsSourceFile, tsNode.jsDoc, '\n'); + + if (doclet) { + sourceNode.doclet = doclet; + } + } + + return sourceNode; +} + + +export function parseChildren( + tsSourceFile: TS.SourceFile, + tsNodeChildren: (Array|TS.NodeArray) +): Array { + const sourceChildren: Array = []; + + for (const tsNodeChild of tsNodeChildren) switch (tsNodeChild.kind) { + case TS.SyntaxKind.CommaToken: + continue; + + case TS.SyntaxKind.SyntaxList: + return parseChildren( + tsSourceFile, + tsNodeChild.getChildren(tsSourceFile) + ); + + default: + sourceChildren.push(parse(tsSourceFile, tsNodeChild)); + continue; + } + + return sourceChildren; +} + + +function parseFor( + tsSourceFile: TS.SourceFile, + tsNode: TS.ForStatement +): SourceNode { + const sourceNode = new SourceNode(tsNode.kind); + + sourceNode.children = parseChildren( + tsSourceFile, + tsNode.statement.getChildren(tsSourceFile) + ); + + if (tsNode.initializer) { + sourceNode.text += `${tsNode.initializer.getText(tsSourceFile)};` + } + + if (tsNode.condition) { + sourceNode.text += `${tsNode.condition.getText(tsSourceFile)};` + } + + if (tsNode.incrementor) { + sourceNode.text += `${tsNode.incrementor.getText(tsSourceFile)};` + } + + return sourceNode; +} + + +function parseFunction( + tsSourceFile: TS.SourceFile, + tsNode: TS.FunctionDeclaration +): SourceNode { + const sourceNode = new SourceNode( + tsNode.kind, + tsNode.name ? tsNode.name.getText(tsSourceFile) : '' + ); + + if (tsNode.body) { + sourceNode.children = parseChildren( + tsSourceFile, + tsNode.body.getChildren(tsSourceFile) + ); + } + + return sourceNode; +} + + +function parseIf( + tsSourceFile: TS.SourceFile, + tsNode: TS.IfStatement +): SourceNode { + const sourceNode = new SourceNode( + tsNode.kind, + tsNode.expression.getText(tsSourceFile) + ); + + sourceNode.children = parseChildren( + tsSourceFile, + tsNode.thenStatement.getChildren(tsSourceFile) + ); + + if (tsNode.elseStatement) { + const tsFirstChild = tsNode.elseStatement.getFirstToken(tsSourceFile); + + if ( + tsFirstChild && + TS.isIfStatement(tsFirstChild) && + tsNode.elseStatement.getChildCount(tsSourceFile) === 1 + ) { + const elseIfSourceNode = parseIf(tsSourceFile, tsFirstChild); + + elseIfSourceNode.text = `else ${elseIfSourceNode.text}`; + + sourceNode.children.push(elseIfSourceNode); + } else { + const elseSourceNode = new SourceNode(tsNode.kind, 'else'); + + elseSourceNode.children = parseChildren( + tsSourceFile, + tsNode.elseStatement.getChildren(tsSourceFile) + ); + + sourceNode.children.push(elseSourceNode); + } + } + + return sourceNode; +} + + +function parseInterface( + tsSourceFile: TS.SourceFile, + tsNode: TS.InterfaceDeclaration +): SourceNode { + const sourceNode = new SourceNode( + tsNode.kind, + tsNode.name.getText(tsSourceFile) + ); + + sourceNode.children = parseChildren(tsSourceFile, tsNode.members); + + if (tsNode.typeParameters) { + sourceNode.text += `<${joinNodeArray(tsSourceFile, tsNode.typeParameters)}>`; + } + + if (tsNode.heritageClauses) { + sourceNode.types = [ + joinNodeArray(tsSourceFile, tsNode.heritageClauses) + ]; + } + + return sourceNode; +} + + +function parseModule( + tsSourceFile: TS.SourceFile, + tsNode: TS.ModuleDeclaration +): SourceNode { + const sourceNode = new SourceNode( + tsNode.kind, + tsNode.name.getText(tsSourceFile) + ); + + if (tsNode.body) { + sourceNode.children = parseChildren( + tsSourceFile, + tsNode.body.getChildren(tsSourceFile) + ); + } + + return sourceNode; +} + + +function parseVariables( + tsSourceFile: TS.SourceFile, + tsNode: TS.VariableStatement +): SourceNode { + const tsFirstChild = tsNode.getFirstToken(tsSourceFile); + + if (!tsFirstChild) { + return new SourceNode(tsNode.kind); + } + + const sourceNode = new SourceNode( + tsNode.declarationList.kind, + tsFirstChild.getText(tsSourceFile) + ); + + sourceNode.children = parseChildren( + tsSourceFile, + tsNode.declarationList.getChildren(tsSourceFile) + ); + + return sourceNode; +} diff --git a/sources/SourcePosition.d.ts b/sources/SourcePosition.d.ts new file mode 100644 index 0000000..a7387a5 --- /dev/null +++ b/sources/SourcePosition.d.ts @@ -0,0 +1,23 @@ +/* * + * + * Declarations + * + * */ + + +export interface SourcePosition { + column: number; + end: number; + line: number; + start: number; +} + + +/* * + * + * Default Export + * + * */ + + +export default SourcePosition; diff --git a/sources/SourceToken.d.ts b/sources/SourceToken.d.ts new file mode 100644 index 0000000..348f7dd --- /dev/null +++ b/sources/SourceToken.d.ts @@ -0,0 +1,31 @@ +/* * + * + * Imports + * + * */ + + +import type * as TS from 'typescript'; + + +/* * + * + * Declarations + * + * */ + + +export interface SourceToken { + kind: T; + text: string; +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceToken; diff --git a/sources/SourceTree.ts b/sources/SourceTree.ts new file mode 100644 index 0000000..c91cfe5 --- /dev/null +++ b/sources/SourceTree.ts @@ -0,0 +1,145 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import * as SP from './SourceParser'; +import SourceNode from './SourceNode'; + + +/* * + * + * Class + * + * */ + + +export class SourceTree { + + + /* * + * + * Constructor + * + * */ + + + public constructor ( + fileName: string, + sourceCode: string + ) { + this.fileName = fileName; + this.nodes = []; + this.parse(sourceCode); + } + + + /* * + * + * Properties + * + * */ + + + public readonly fileName: string; + + + public readonly nodes: Array; + + + /* * + * + * Functions + * + * */ + + + public parse ( + sourceCode: string, + replace = false + ) { + const nodes = this.nodes; + + if (replace) { + nodes.length = 0; + } + + if (!sourceCode) { + return; + } + + const tsSourceFile = TS.createSourceFile( + '', + sourceCode, + TS.ScriptTarget.Latest + ), + roots = SP.parseChildren(tsSourceFile, tsSourceFile.getChildren()); + + if (roots) { + for (let i = 0, iEnd = roots.length; i < iEnd; ++i) { + nodes.push(roots[i]); + } + } + } + + + public toArray (): Array { + const nodes = this.nodes, + result: Array = []; + + for ( + let i = 0, + iEnd = nodes.length, + nodesChildren: Array; + i < iEnd; + ++i + ) { + nodesChildren = nodes[i].toArray(); + + for ( + let j = 0, + jEnd = nodesChildren.length; + j < jEnd; + ++j + ) { + result.push(nodesChildren[j]); + } + } + + return result; + } + + + public toString (): string { + const nodes = this.nodes; + + let text = ''; + + for (let i = 0, iEnd = nodes.length; i < iEnd; ++i) { + text += nodes[i].toString(); + } + + return text; + } +} + + +/* * + * + * Default Export + * + * */ + + +export default SourceTree; diff --git a/sources/Utilities.ts b/sources/Utilities.ts new file mode 100644 index 0000000..4a0d31e --- /dev/null +++ b/sources/Utilities.ts @@ -0,0 +1,328 @@ +/** + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; + + +/* * + * + * Declarations + * + * */ + + +export interface DocumentedNode extends TS.Node { + jsDoc: Array; +} + + +export interface ExpressionNode extends TS.Node { + expression: TS.Expression; +} + + +export type JSDocTagKind = ( + TS.SyntaxKind.JSDocAugmentsTag| + TS.SyntaxKind.JSDocAuthorTag| + TS.SyntaxKind.JSDocCallbackTag| + TS.SyntaxKind.JSDocClassTag| + TS.SyntaxKind.JSDocDeprecatedTag| + TS.SyntaxKind.JSDocEnumTag| + TS.SyntaxKind.JSDocImplementsTag| + TS.SyntaxKind.JSDocOverrideTag| + TS.SyntaxKind.JSDocParameterTag| + TS.SyntaxKind.JSDocPrivateTag| + TS.SyntaxKind.JSDocPropertyTag| + TS.SyntaxKind.JSDocProtectedTag| + TS.SyntaxKind.JSDocPublicTag| + TS.SyntaxKind.JSDocReadonlyTag| + TS.SyntaxKind.JSDocReturnTag| + TS.SyntaxKind.JSDocSeeTag| + TS.SyntaxKind.JSDocTag| + TS.SyntaxKind.JSDocTemplateTag| + TS.SyntaxKind.JSDocThisTag| + TS.SyntaxKind.JSDocTypeTag| + TS.SyntaxKind.JSDocTypedefTag +); + + +export type StatementKind = ( + TS.SyntaxKind.BreakStatement| + TS.SyntaxKind.ContinueStatement| + TS.SyntaxKind.DebuggerStatement| + TS.SyntaxKind.DoStatement| + TS.SyntaxKind.EmptyStatement| + TS.SyntaxKind.ExpressionStatement| + TS.SyntaxKind.ForStatement| + TS.SyntaxKind.ForInStatement| + TS.SyntaxKind.ForOfStatement| + TS.SyntaxKind.IfStatement| + TS.SyntaxKind.LabeledStatement| + TS.SyntaxKind.ReturnStatement| + TS.SyntaxKind.SwitchStatement| + TS.SyntaxKind.ThrowStatement| + TS.SyntaxKind.TryStatement| + TS.SyntaxKind.VariableStatement| + TS.SyntaxKind.WhileStatement +); + + +export type UnknownObject = Record; + + +/* * + * + * Constants + * + * */ + + +export const LINE_BREAKS = /\r\n|\r|\n/gu; + + +export const PARAGRAPHS = new RegExp(`(?:${LINE_BREAKS.source}){2,2}`, 'gu'); + + +export const SPACES = /[ \t]/gu; + + +/* * + * + * Functions + * + * */ + + +export function extractTypes( + tsSourceFile: TS.SourceFile, + tsNode: TS.Node +): Array { + const tsChildren = tsNode.getChildren(tsSourceFile), + types: Array = []; + + for (const tsChild of tsChildren) { + if (TS.isTypeReferenceNode(tsChild)) { + types.push(tsChild.getText(tsSourceFile)); + } + } + + return types; +} + + +export function extractFirstLine( + text: string +): string { + return text.split(LINE_BREAKS)[0]; +} + + +export function extractLastLine( + text: string +): string { + const lines = text.split(LINE_BREAKS); + return lines[lines.length-1]; +} + + +export function detectLineBreak( + text: string +): (string|undefined) { + return text.match(new RegExp(LINE_BREAKS.source, 'u'))?.[0] +} + + +/** + * Returns a indented string, that fits into a specific width and spans over + * several lines. + * + * @param text + * The string to pad. + * + * @param indent + * The prefix for each line. + * + * @param wrap + * The maximum width of the padded string. + */ +export function indent ( + indent: number, + prefix: string, + text: string, + wrap?: number +): string { + + const lb = detectLineBreak(text) || '\n'; + + prefix = pad(indent, prefix); + + if (!wrap) { + return prefix + text.replace(LINE_BREAKS, `${lb}${prefix}`); + } + + const fragments = text + .replace(PARAGRAPHS, ' \x00 ') // paragraphs + .replace(LINE_BREAKS, ' \x05 ') // single break + .trim() + .split(SPACES); + + let codeBlock = false, + newLine = true, + line = prefix, + newParagraph = false, + paddedStr = ''; + + for (const fragment of fragments) { + + if (fragment === '\x00') { + newLine = true; + newParagraph = true; + paddedStr += line.trimRight() + lb + prefix.trimRight() + lb; + continue; + } + + if (fragment === '\x05') { + if (codeBlock) { + newLine = true; + paddedStr += line.trimRight() + lb; + } else if (newParagraph) { + newLine = true; + paddedStr += prefix.trimRight() + lb; + } + continue; + } + + if (fragment.startsWith('```')) { + codeBlock = !codeBlock; + + if (!newLine) { + newLine = true; + paddedStr += line.trimRight() + lb; + } + } + + if ( + !codeBlock && + !newLine && + line.trimRight().length + 1 + fragment.length > wrap + ) { + newLine = true; + paddedStr += line.trimRight() + lb; + } + + if (newLine) { + newLine = false; + line = prefix + fragment; + } else { + line += ' ' + fragment; + } + + if (fragment && newParagraph) { + newParagraph = false; + } + } + + return newLine ? paddedStr : paddedStr + line.trimRight(); +} + + +export function isDocumentedNode ( + node: T +): node is (T&DocumentedNode) { + return ( + typeof (node as unknown as DocumentedNode).jsDoc === 'object' + ) +} + + +export function isExpressionNode ( + node: T +): node is (T&ExpressionNode) { + return ( + typeof (node as unknown as ExpressionNode).expression === 'object' + ) +} + + +export function isNodeClass ( + node: T, + nodeClass: ('Assignment'|'Declaration'|'Expression'|'Signature'|'Statement') +): boolean { + const kindClass: (string|undefined) = TS.SyntaxKind[node.kind]; + return !!kindClass && kindClass.endsWith(nodeClass); +} + + +export function isNodeStatement ( + node: T +): node is (T&StatementKind) { + return ( + TS.isBreakOrContinueStatement(node) || + TS.isDebuggerStatement(node) || + TS.isDoStatement(node) || + TS.isEmptyStatement(node) || + TS.isExpressionStatement(node) || + TS.isForInStatement(node) || + TS.isForOfStatement(node) || + TS.isForStatement(node) || + TS.isIfStatement(node) || + TS.isLabeledStatement(node) || + TS.isReturnStatement(node) || + TS.isSwitchStatement(node) || + TS.isThrowStatement(node) || + TS.isTryStatement(node) || + TS.isVariableStatement(node) || + TS.isWhileStatement(node) + ); +} + + +export function pad(indent: number, suffix: string = ''): string { + return ' '.repeat(indent) + suffix; +} + + +export function removeBreaks ( + text: string +): string { + return text.replace(LINE_BREAKS, ' ').trim(); +} + +export function trimAll ( + text: string, + keepParagraphs = false +): string { + const lb = detectLineBreak(text) || '\n'; + + if (keepParagraphs) { + return text + .replace(PARAGRAPHS, ' \x00 ') + .replace(/\s+/gu, ' ') + .trim() + .replace(/ ?\x00 ?/, `${lb}${lb}`); + } + + return text.replace(/\s+/gu, ' ').trim(); +} + + +export function trimBreaks ( + text: string +): string { + return text.replace(new RegExp( + `^(?:${LINE_BREAKS.source}){1,}|(?:${LINE_BREAKS.source}){1,}$`, + 'gu' + ), ''); +} diff --git a/sources/rules/debug.ts b/sources/rules/debug.ts new file mode 100644 index 0000000..154b844 --- /dev/null +++ b/sources/rules/debug.ts @@ -0,0 +1,98 @@ +/** + * @fileoverview Debugs TypeScript tokens. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceToken from '../SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +type DebugContext = RuleContext; + + +interface DebugOptions extends RuleOptions { + onlyKindOf?: Array; +} + + +/* * + * + * Constants + * + * */ + + +const optionsDefaults: RuleOptions = {} + + +const optionsSchema = { + 'onlyKindOf': { + 'type': 'array' + } +} + + +/* * + * + * Functions + * + * */ + + +function lint ( + context: DebugContext +) { + const onlyKindOf = context.options.onlyKindOf, + sourceTree = context.sourceTree, + sourceNodes = sourceTree.toArray(); + + for (const sourceNode of sourceNodes) { + + if (onlyKindOf && !onlyKindOf.includes(sourceNode.kind)) { + continue; + } + + console.log( + sourceNode.kind, + TS.SyntaxKind[sourceNode.kind], + sourceNode.text, + sourceNode.types, + sourceNode.doclet + ); + } +} + + +/* * + * + * Default Export + * + * */ + + +export = RuleContext.setupRuleExport( + 'layout', + optionsSchema, + optionsDefaults, + lint +); diff --git a/sources/rules/generic-array-type.ts b/sources/rules/generic-array-type.ts new file mode 100644 index 0000000..28335d8 --- /dev/null +++ b/sources/rules/generic-array-type.ts @@ -0,0 +1,193 @@ +/** + * @fileoverview Array types should always be written in generic syntax to avoid + * any confusion with array assignments, array indexer, or type selectors. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as ESLint from 'eslint'; +import * as TS from 'typescript'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceLine from '../SourceLine'; +import SourcePosition from '../SourcePosition'; +import SourceToken from '../SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +type GenericArrayTypeContext = RuleContext; + + +/* * + * + * Constants + * + * */ + + +const message = [ + 'Do not use the [] shortcut for the array type.', + 'Instead write the generic Array<...> to improve readability.' +].join(' '); + +const optionsDefaults: RuleOptions = {}; + +const optionsSchema = {}; + + +/* * + * + * Functions + * + * */ + + +function createFixer( + context: GenericArrayTypeContext, + linesToFix: Array +): ESLint.Rule.ReportFixer { + return (): (ESLint.Rule.Fix|null) => { + const code = context.sourceCode, + fix: Array = [], + range: ESLint.AST.Range = [ 0, code.raw.length + 256 ], + lines = code.lines; + + let firstToken: SourceToken, + secondToken: SourceToken, + thirdToken: SourceToken, + tokenReplacements: Array, + tokens: Array; + + for (const l of lines) { + + if (linesToFix.includes(l)) { + tokens = l.tokens; + + for (let i = 0, iEnd = tokens.length - 2; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i+1]; + thirdToken = tokens[i+2]; + + if (isMatch(firstToken, secondToken, thirdToken)) { + tokenReplacements = [{ + kind: TS.SyntaxKind.Identifier, + text: 'Array' + }, { + kind: TS.SyntaxKind.LessThanToken, + text: '<' + }, + firstToken, + { + kind: TS.SyntaxKind.GreaterThanToken, + text: '>' + }]; + tokens.splice(i, 3, ...tokenReplacements); + iEnd = tokens.length - 2; + } + } + } + + fix.push(l.toString()); + } + + return { range, text: fix.join(code.lineBreak) }; + }; +} + + +function isMatch( + firstToken: SourceToken, + secondToken: SourceToken, + thirdToken: SourceToken +): boolean { + return ( + secondToken.kind === TS.SyntaxKind.OpenBracketToken && + thirdToken.kind === TS.SyntaxKind.CloseBracketToken && + ( + firstToken.kind === TS.SyntaxKind.AnyKeyword || + firstToken.kind === TS.SyntaxKind.BooleanKeyword || + firstToken.kind === TS.SyntaxKind.BigIntKeyword || + firstToken.kind === TS.SyntaxKind.CloseBracketToken || + firstToken.kind === TS.SyntaxKind.CloseParenToken || + firstToken.kind === TS.SyntaxKind.GreaterThanToken || + firstToken.kind === TS.SyntaxKind.NullKeyword || + firstToken.kind === TS.SyntaxKind.NumberKeyword || + firstToken.kind === TS.SyntaxKind.ObjectKeyword || + firstToken.kind === TS.SyntaxKind.StringKeyword || + firstToken.kind === TS.SyntaxKind.SymbolKeyword || + firstToken.kind === TS.SyntaxKind.UndefinedKeyword || + firstToken.kind === TS.SyntaxKind.UnknownKeyword || + firstToken.kind === TS.SyntaxKind.VoidKeyword + ) + ); +} + + + +function lint ( + context: GenericArrayTypeContext +): void { + const code = context.sourceCode, + lines = code.lines, + linesToFix: Array = []; + + let firstToken: SourceToken, + secondToken: SourceToken, + thirdToken: SourceToken, + tokens: Array; + + for (const line of lines) { + tokens = line.tokens; + + for (let i = 0, iEnd = tokens.length - 2; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i+1]; + thirdToken = tokens[i+2]; + + if (isMatch(firstToken, secondToken, thirdToken)) { + const position = code.getTokenPosition(line, secondToken); + + if (position) { + context.prepareReport( + position, + message + ); + linesToFix.push(line); + } + } + + } + } + + context.sendReports(createFixer(context, linesToFix)); +} + +/* * + * + * Default Export + * + * */ + +export = RuleContext.setupRuleExport( + 'layout', + optionsSchema, + optionsDefaults, + lint, + true +); diff --git a/sources/rules/no-import-effects.ts b/sources/rules/no-import-effects.ts new file mode 100644 index 0000000..741b0bb --- /dev/null +++ b/sources/rules/no-import-effects.ts @@ -0,0 +1,167 @@ +/** + * @fileoverview Imports should not be anonymous. Move desired side effects into + * compose functions and call these instead. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceLine from '../SourceLine'; +import SourceToken from '../SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +type NoImportEffectsContext = RuleContext; + + +/* * + * + * Constants + * + * */ + + +const messageTemplate = [ + 'Imports should not be anonymous.', + 'Create and call a composer for side effects.' +].join(' '); + + +const optionsDefaults: RuleOptions = {}; + + +const optionsSchema = {}; + + +/* * + * + * Functions + * + * */ + + +function lint( + context: NoImportEffectsContext +): void { + const code = context.sourceCode, + importsToCheck: Array = [], + tokensToCheck: Array<[SourceLine, SourceToken]> = []; + + let firstToken: SourceToken, + secondToken: SourceToken, + tokens: Array; + + for (const line of code.lines) { + + if (line.getIndent() !== 0) { + continue; + } + + tokens = line.getEssentialTokens(); + + for (let i = 0, iEnd = tokens.length; i < iEnd; ++i) { + firstToken = tokens[i]; + + if (firstToken.kind === TS.SyntaxKind.DeleteKeyword) { + const identifierIndex = line + .getTokenKinds(i) + .indexOf(TS.SyntaxKind.Identifier) + i; + + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } else if ( + firstToken.kind === TS.SyntaxKind.EqualsToken + ) { + const identifierIndex = line + .getTokenKinds(0, i) + .indexOf(TS.SyntaxKind.Identifier); + + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } + + secondToken = tokens[i+1]; + + if (!secondToken) { + continue; + } + + if ( + firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.Identifier + ) { + importsToCheck.push(secondToken.text); + } else if ( + firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.OpenBraceToken + ) { + let previousToken: (SourceToken|undefined); + + for (const thisToken of tokens.slice(i+2)) { + + if ( + previousToken && + previousToken.kind === TS.SyntaxKind.Identifier && + ( + thisToken.kind === TS.SyntaxKind.CommaToken || + thisToken.kind === TS.SyntaxKind.CloseBraceToken || + thisToken.kind === TS.SyntaxKind.CloseBracketToken + ) + ) { + importsToCheck.push(previousToken.text); + } + + previousToken = thisToken; + } + } + } + } + + for (const token of tokensToCheck) { + if (importsToCheck.includes(token[1].text)) { + const position = code.getTokenPosition(token[0], token[1]); + + if (position) { + context.prepareReport(position, messageTemplate); + } + } + } + + context.sendReports(); +} + + +/* * + * + * Default Export + * + * */ + + +export = RuleContext.setupRuleExport( + 'problem', + optionsSchema, + optionsDefaults, + lint, + false +); diff --git a/sources/rules/no-import-modification.ts b/sources/rules/no-import-modification.ts new file mode 100644 index 0000000..4cf58d0 --- /dev/null +++ b/sources/rules/no-import-modification.ts @@ -0,0 +1,167 @@ +/** + * @fileoverview Imports should not be immediately modified as this would + * prevent async import. Provide a composer to allow modifications by consumers. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceLine from '../SourceLine'; +import SourceToken from '../SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +type NoImportModificationContext = RuleContext; + + +/* * + * + * Constants + * + * */ + + +const messageTemplate = [ + 'Imports should not be immediately modified.', + 'Create a composer.' +].join(' '); + + +const optionsDefaults: RuleOptions = {}; + + +const optionsSchema = {}; + + +/* * + * + * Functions + * + * */ + + +function lint( + context: NoImportModificationContext +): void { + const code = context.sourceCode, + importsToCheck: Array = [], + tokensToCheck: Array<[SourceLine, SourceToken]> = []; + + let firstToken: SourceToken, + secondToken: SourceToken, + tokens: Array; + + for (const line of code.lines) { + + if (line.getIndent() !== 0) { + continue; + } + + tokens = line.getEssentialTokens(); + + for (let i = 0, iEnd = tokens.length; i < iEnd; ++i) { + firstToken = tokens[i]; + + if (firstToken.kind === TS.SyntaxKind.DeleteKeyword) { + const identifierIndex = line + .getTokenKinds(i) + .indexOf(TS.SyntaxKind.Identifier) + i; + + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } else if ( + firstToken.kind === TS.SyntaxKind.EqualsToken + ) { + const identifierIndex = line + .getTokenKinds(0, i) + .indexOf(TS.SyntaxKind.Identifier); + + if (identifierIndex >= 0) { + tokensToCheck.push([line, line.tokens[identifierIndex]]); + } + } + + secondToken = tokens[i+1]; + + if (!secondToken) { + continue; + } + + if ( + firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.Identifier + ) { + importsToCheck.push(secondToken.text); + } else if ( + firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.OpenBraceToken + ) { + let previousToken: (SourceToken|undefined); + + for (const thisToken of tokens.slice(i+2)) { + + if ( + previousToken && + previousToken.kind === TS.SyntaxKind.Identifier && + ( + thisToken.kind === TS.SyntaxKind.CommaToken || + thisToken.kind === TS.SyntaxKind.CloseBraceToken || + thisToken.kind === TS.SyntaxKind.CloseBracketToken + ) + ) { + importsToCheck.push(previousToken.text); + } + + previousToken = thisToken; + } + } + } + } + + for (const token of tokensToCheck) { + if (importsToCheck.includes(token[1].text)) { + const position = code.getTokenPosition(token[0], token[1]); + + if (position) { + context.prepareReport(position, messageTemplate); + } + } + } + + context.sendReports(); +} + + +/* * + * + * Default Export + * + * */ + + +export = RuleContext.setupRuleExport( + 'problem', + optionsSchema, + optionsDefaults, + lint, + false +); diff --git a/sources/rules/no-import-type.ts b/sources/rules/no-import-type.ts new file mode 100644 index 0000000..279d373 --- /dev/null +++ b/sources/rules/no-import-type.ts @@ -0,0 +1,154 @@ +/** + * @fileoverview Explicitly type imports are not necessary because TypeScript + * will automatically remove type-only used imports during transpilation. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as ESLint from 'eslint'; +import * as TS from 'typescript'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceLine from '../SourceLine'; +import SourceToken from '../SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +type NoImportTypeTypeContext = RuleContext; + + +/* * + * + * Constants + * + * */ + + +const message = [ + 'Explicitly type imports are not necessary.', + 'Instead write the generic Array<...> to improve readability.' +].join(' '); + +const optionsDefaults: RuleOptions = {}; + +const optionsSchema = {}; + + +/* * + * + * Functions + * + * */ + + +function createFixer( + context: NoImportTypeTypeContext, + linesToFix: Array +): ESLint.Rule.ReportFixer { + return (): ESLint.Rule.Fix => { + const code = context.sourceCode, + fix: Array = [], + range: ESLint.AST.Range = [ 0, code.raw.length + 256 ], + lines = code.lines; + + let firstToken: SourceToken, + secondToken: SourceToken, + thirdToken: SourceToken, + tokens: Array; + + for (const l of lines) { + + if (linesToFix.includes(l)) { + tokens = l.tokens; + + for (let i = 0, iEnd = tokens.length - 2; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i+1]; + thirdToken = tokens[i+2]; + + if ( + firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.WhitespaceTrivia && + thirdToken.kind === TS.SyntaxKind.TypeKeyword + ) { + tokens.splice(i + 1, 2); + iEnd = tokens.length - 2; + } + } + } + + fix.push(l.toString()); + } + + return { range, text: fix.join(code.lineBreak) }; + }; +} + +function lint ( + context: NoImportTypeTypeContext +): void { + const code = context.sourceCode, + lines = code.lines, + linesToFix: Array = []; + + let firstToken: SourceToken, + secondToken: SourceToken, + tokens: Array; + + for (const line of lines) { + tokens = line.getEssentialTokens(); + + for (let i = 0, iEnd = tokens.length - 1; i < iEnd; ++i) { + firstToken = tokens[i]; + secondToken = tokens[i+1]; + + if ( + firstToken.kind === TS.SyntaxKind.ImportKeyword && + secondToken.kind === TS.SyntaxKind.TypeKeyword + ) { + const firstPosition = code.getTokenPosition(line, firstToken), + secondPosition = code.getTokenPosition(line, secondToken); + + if (firstPosition && secondPosition) { + context.prepareReport( + secondPosition, + message + ); + linesToFix.push(line); + } + } + } + } + + context.sendReports(createFixer(context, linesToFix)); +} + +/* * + * + * Default Export + * + * */ + +export = RuleContext.setupRuleExport( + 'layout', + optionsSchema, + optionsDefaults, + lint, + true +); diff --git a/sources/rules/no-optional-chaining.ts b/sources/rules/no-optional-chaining.ts new file mode 100644 index 0000000..691b31a --- /dev/null +++ b/sources/rules/no-optional-chaining.ts @@ -0,0 +1,106 @@ +/** + * @fileoverview Do not use optional chaining because it bloats transpiled code. + * Instead use the `pick` function or `&&` conditions. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as TS from 'typescript'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceToken from '../SourceToken'; + + +/* * + * + * Declarations + * + * */ + + +type GenericArrayTypeContext = RuleContext; + + +/* * + * + * Constants + * + * */ + + +const message = [ + 'Do not use optional chaining.', + 'Instead use the `pick` function or `&&` conditions.' +].join(' '); + + +const optionsDefaults: RuleOptions = {}; + + +const optionsSchema = {}; + + +/* * + * + * Functions + * + * */ + + +function lint ( + context: GenericArrayTypeContext +): void { + const code = context.sourceCode, + lines = code.lines; + + let tokens: Array; + + for (const line of lines) { + tokens = line.tokens; + + for ( + let index = 0, + indexEnd = tokens.length - 2, + firstToken: SourceToken; + index < indexEnd; + ++index + ) { + firstToken = tokens[index]; + + if (firstToken.kind === TS.SyntaxKind.QuestionDotToken) { + const position = code.getTokenPosition(line, firstToken); + + if (position) { + context.prepareReport(position, message); + } + } + + } + } + + context.sendReports(); +} + +/* * + * + * Default Export + * + * */ + +export = RuleContext.setupRuleExport( + 'problem', + optionsSchema, + optionsDefaults, + lint, + false +); diff --git a/sources/rules/pretty-length.ts b/sources/rules/pretty-length.ts new file mode 100644 index 0000000..e8d29e3 --- /dev/null +++ b/sources/rules/pretty-length.ts @@ -0,0 +1,191 @@ +/** + * @fileoverview Limit and autofix max length. + * @author Sophie Bremer + */ + + +'use strict'; + + +/* * + * + * Imports + * + * */ + + +import * as ESLint from 'eslint'; +import RuleContext from '../RuleContext'; +import RuleOptions from '../RuleOptions'; +import SourceLine from '../SourceLine'; + + +/* * + * + * Declarations + * + * */ + + +type PrettyLengthContext = RuleContext; + + +interface PrettyLengthOptions extends RuleOptions { + ignorePattern?: string; + indentSize?: number; + maximalLength: number; +} + + +/* * + * + * Constants + * + * */ + + +const messageTemplate = 'Line exceeds limit of {0} characters.'; + + +const optionsDefaults: PrettyLengthOptions = { + maximalLength: 80 +}; + + +const optionsSchema = { + 'ignorePattern': { + 'type': 'string' + }, + 'maximalLength': { + 'type': 'integer' + } +}; + + +/* * + * + * Functions + * + * */ + + +function createFixer ( + context: PrettyLengthContext, + fixLines: Array +): ESLint.Rule.ReportFixer { + return (): ESLint.Rule.Fix => { + const code = context.sourceCode, + fix: Array = [], + range: ESLint.AST.Range = [ 0, code.raw.length + 256 ], + lines = code.lines, + { + ignorePattern, + maximalLength + } = context.options, + ignoreRegExp = (ignorePattern ? new RegExp(ignorePattern) : void 0); + + let text: string; + + for (const l of lines) { + text = l.toString(); + + if ( + fixLines.includes(l) && + ( + !ignoreRegExp || + !ignoreRegExp.test(text) + ) + ) { + fix.push(l.toString(maximalLength)); + } else { + fix.push(text); + } + } + + return { range, text: fix.join(code.lineBreak) }; + }; +} + + +function lint ( + context: PrettyLengthContext +): void { + const code = context.sourceCode, + { + ignorePattern, + maximalLength + } = context.options, + ignoreRegExp = (ignorePattern ? new RegExp(ignorePattern) : void 0), + lines = code.lines, + fixLines: Array = [], + message = messageTemplate.replace('{0}', `${maximalLength}`); + + let maximalLineLength: number; + + for (const line of lines) { + + if ( + ignoreRegExp && + ignoreRegExp.test(line.toString()) + ) { + continue; + } + + maximalLineLength = line.getMaximalLength(); + + if (maximalLineLength > maximalLength) { + const position = code.getLinePosition(line); + + if (position) { + const wrappedLines = line.getWrappedLines(); + + if (wrappedLines.length === 1) { + // only lines with multiline comments for now + continue; + } + + let lineIndex = 0; + + for (const wrappedLine of wrappedLines) { + + if ( + wrappedLine.length > maximalLength && + wrappedLine.split(/\s+/g).length > 1 + ) { + context.prepareReport( + { + column: maximalLength + 1, + end: position.end, + line: position.line + lineIndex, + start: position.start + }, + message + ` ${maximalLineLength}` + ); + + fixLines.push(line); + } + + ++lineIndex; + } + } + } + } + + context.sendReports(createFixer(context, fixLines)); +} + + +/* * + * + * Default Export + * + * */ + + +export = RuleContext.setupRuleExport( + 'layout', + optionsSchema, + optionsDefaults, + lint, + true +); diff --git a/sources/tsconfig.json b/sources/tsconfig.json new file mode 100644 index 0000000..28f46d5 --- /dev/null +++ b/sources/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + // "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "es6", + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "rootDir": "./", + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + "outDir": "../lib/", + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + // "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/tests/sources/index.ts b/tests/sources/index.ts new file mode 100644 index 0000000..cdaa7a9 --- /dev/null +++ b/tests/sources/index.ts @@ -0,0 +1,7 @@ +import * as U from '../../lib/Utilities'; + +const loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; +const loremIpsumWithBreaks = loremIpsum.replace(/\. /g, '.\n\n'); + +console.log(U.indent(4, ' * ', loremIpsum, 60)); +console.log(U.indent(4, ' * ', loremIpsumWithBreaks, 60)); diff --git a/tests/sources/tsconfig.json b/tests/sources/tsconfig.json new file mode 100644 index 0000000..13bbe5a --- /dev/null +++ b/tests/sources/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../sources/tsconfig.json", + "compilerOptions": { + "outDir": ".", + "rootDir": "." + }, + "files": [ + "index.ts" + ], + "references": [{ + "path": "../../sources/tsconfig.json" + }] +}