diff --git a/.eslintrc.json b/.eslintrc.json index 30149ce..e0324ef 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,11 @@ "es2021": true, "node": true }, - "extends": "standard", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" diff --git a/package.json b/package.json index 2bd36fe..fd13921 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,16 @@ "readable-stream": "^4.5.2" }, "devDependencies": { + "@eslint/js": "^9.3.0", "@types/chai": "latest", "@types/content-type": "^1.1.8", + "@types/eslint__js": "^8.42.3", "@types/mocha": "latest", "@types/readable-stream": "^4.0.11", + "@typescript-eslint/eslint-plugin": "^7.11.0", + "@typescript-eslint/parser": "^7.11.0", "chai": "^5.0.3", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-n": "^16.6.2", diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/mocompiler.js b/src/mocompiler.ts similarity index 69% rename from src/mocompiler.js rename to src/mocompiler.ts index 049ab62..8a9b5fe 100644 --- a/src/mocompiler.js +++ b/src/mocompiler.ts @@ -1,42 +1,19 @@ import encoding from 'encoding'; import { HEADERS, formatCharset, generateHeader, compareMsgid } from './shared.js'; import contentType from 'content-type'; +import {GetTextTranslation, GetTextTranslations, Size, TranslationBuffers, Translations} from "./types.js"; +import { Transform } from 'readable-stream'; -/** - * @typedef {import('node:stream').Transform} Transform - * @typedef {import('./types.js').GetTextTranslation} GetTextTranslation - * @typedef {import('./types.js').GetTextTranslations} GetTextTranslations - * @typedef {import('./types.js').Translations} Translations - * @typedef {import('./types.js').WriteFunc} WriteFunc - */ - -/** - * @typedef {Object} Size Data about the size of the compiled MO object. - * @property {number} msgid The size of the msgid section. - * @property {number} msgstr The size of the msgstr section. - * @property {number} total The total size of the compiled MO object. - */ - -/** - * @typedef {{ msgid: Buffer, msgstr: Buffer }} TranslationBuffers A translation object partially parsed. - */ - -/** - * - * @typedef {Object} CompilerOptions MO compiler options - * @property {'be'|'le'} [endian='le'] Endianness of the output buffer. Default is 'le' - */ /** * Exposes general compiler function. Takes a translation * object as a parameter and returns binary MO object * - * @param {GetTextTranslations} table Translation object - * @param {CompilerOptions} [options] MO compiler options - * @return {Buffer} Compiled binary MO object + * @param table Translation object + * @return Compiled binary MO object */ -export default function (table, options = { endian: 'le' }) { - const compiler = new Compiler(table, options); +export default function (table: GetTextTranslations): Buffer { + const compiler = new Compiler(table); return compiler.compile(); } @@ -46,8 +23,8 @@ export default function (table, options = { endian: 'le' }) { * @param {Record} headers the headers * @return {Record} The prepared header */ -function prepareMoHeaders (headers) { - return Object.keys(headers).reduce((result, key) => { +function prepareMoHeaders (headers: Record): Record { + return Object.keys(headers).reduce((result: Record, key: string) => { const lowerKey = key.toLowerCase(); if (HEADERS.has(lowerKey)) { @@ -71,10 +48,10 @@ function prepareMoHeaders (headers) { * @param {Translations} translations * @return {Translations} */ -function prepareTranslations (translations) { +function prepareTranslations (translations: Translations): Translations { return Object.keys(translations).reduce((result, msgctxt) => { const context = translations[msgctxt]; - const msgs = Object.keys(context).reduce((result, msgid) => { + const msgs = Object.keys(context).reduce((result: Record, msgid) => { const TranslationMsgstr = context[msgid].msgstr; const hasTranslation = TranslationMsgstr.some(item => !!item.length); @@ -83,44 +60,36 @@ function prepareTranslations (translations) { } return result; - }, /** @type {Record} */({})); + },{}); if (Object.keys(msgs).length) { result[msgctxt] = msgs; } return result; - }, /** @type {Translations} */({})); + }, {} as Translations); } + /** - * Creates a MO compiler object. - * @this {Compiler & Transform} + * Creates a MO compiler object * - * @param {GetTextTranslations} [table] Translation table as defined in the README - * @param {CompilerOptions} [options] MO compiler options + * @param table Translation table as defined in the README */ -function Compiler (table, options = { endian: 'le' }) { - /** @type {GetTextTranslations} _table The translation table */ +function Compiler (this: Compiler & Transform, table: GetTextTranslations) { + /** The translation table */ this._table = { charset: undefined, translations: prepareTranslations(table?.translations ?? {}), headers: prepareMoHeaders(table?.headers ?? {}) - }; + } as GetTextTranslations; this._translations = []; - /** - * @type {WriteFunc} - */ - this._writeFunc = options?.endian === 'be' ? 'writeUInt32BE' : 'writeUInt32LE'; + this._writeFunc = 'writeUInt32LE'; this._handleCharset(); - /** - * Magic bytes for the generated binary data - * @type {number} MAGIC file header magic value of mo file - */ this.MAGIC = 0x950412de; } @@ -197,10 +166,10 @@ Compiler.prototype._generateList = function () { /** * Calculate buffer size for the final binary object * - * @param {TranslationBuffers[]} list An array of translation strings from _generateList - * @return {Size} Size data of {msgid, msgstr, total} + * @param list An array of translation strings from _generateList + * @return Size data of {msgid, msgstr, total} */ -Compiler.prototype._calculateSize = function (list) { +Compiler.prototype._calculateSize = function (list: TranslationBuffers[]): Size { let msgidLength = 0; let msgstrLength = 0; @@ -231,11 +200,11 @@ Compiler.prototype._calculateSize = function (list) { /** * Generates the binary MO object from the translation list * - * @param {TranslationBuffers[]} list translation list - * @param {Size} size Byte size information - * @return {Buffer} Compiled MO object + * @param list translation list + * @param size Byte size information + * @return Compiled MO object */ -Compiler.prototype._build = function (list, size) { +Compiler.prototype._build = function (list: TranslationBuffers[], size: Size): Buffer { const returnBuffer = Buffer.alloc(size.total); let curPosition = 0; let i; @@ -267,8 +236,8 @@ Compiler.prototype._build = function (list, size) { for (i = 0, len = list.length; i < len; i++) { const msgidLength = /** @type {Buffer} */(/** @type {unknown} */(list[i].msgid)); msgidLength.copy(returnBuffer, curPosition); - returnBuffer[this._writeFunc](list[i].msgid.length, 28 + i * 8); - returnBuffer[this._writeFunc](curPosition, 28 + i * 8 + 4); + returnBuffer.writeUInt32LE(list[i].msgid.length, 28 + i * 8); + returnBuffer.writeUInt32LE(curPosition, 28 + i * 8 + 4); returnBuffer[curPosition + list[i].msgid.length] = 0x00; curPosition += list[i].msgid.length + 1; } @@ -277,8 +246,8 @@ Compiler.prototype._build = function (list, size) { for (i = 0, len = list.length; i < len; i++) { const msgstrLength = /** @type {Buffer} */(/** @type {unknown} */(list[i].msgstr)); msgstrLength.copy(returnBuffer, curPosition); - returnBuffer[this._writeFunc](list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8); - returnBuffer[this._writeFunc](curPosition, 28 + (4 + 4) * list.length + i * 8 + 4); + returnBuffer.writeUInt32LE(list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8); + returnBuffer.writeUInt32LE(curPosition, 28 + (4 + 4) * list.length + i * 8 + 4); returnBuffer[curPosition + list[i].msgstr.length] = 0x00; curPosition += list[i].msgstr.length + 1; } diff --git a/src/moparser.js b/src/moparser.ts similarity index 92% rename from src/moparser.js rename to src/moparser.ts index 263efdb..cd7cf7a 100644 --- a/src/moparser.js +++ b/src/moparser.ts @@ -1,13 +1,7 @@ import encoding from 'encoding'; import { formatCharset, parseHeader } from './shared.js'; -/** - * @typedef {import('./types.js').GetTextTranslations} GetTextTranslations - * @typedef {import('./types.js').GetTextTranslation} GetTextTranslation - * @typedef {import('./types.js').Translations} Translations - * @typedef {import('./types.js').WriteFunc} WriteFunc - * @typedef {import('./types.js').ReadFunc} ReadFunc - */ +import type {Parser,GetTextTranslations,GetTextTranslation,Translations,WriteFunc,ReadFunc,} from './types.js'; /** * Parses a binary MO object into translation table @@ -15,7 +9,7 @@ import { formatCharset, parseHeader } from './shared.js'; * @param {Buffer} buffer Binary MO object * @param {string} [defaultCharset] Default charset to use */ -export default function (buffer, defaultCharset) { +export default function (buffer: any, defaultCharset: string | undefined) { const parser = new Parser(buffer, defaultCharset); return parser.parse(); @@ -28,7 +22,7 @@ export default function (buffer, defaultCharset) { * @param {Buffer|null} fileContents Binary MO object * @param {string} [defaultCharset] Default charset to use */ -function Parser (fileContents, defaultCharset = 'iso-8859-1') { +function Parser (this: Parser, fileContents: Buffer | null, defaultCharset: string = 'iso-8859-1') { this._fileContents = fileContents; this._charset = defaultCharset; diff --git a/src/pocompiler.js b/src/pocompiler.ts similarity index 69% rename from src/pocompiler.js rename to src/pocompiler.ts index b107e88..dd842e8 100644 --- a/src/pocompiler.js +++ b/src/pocompiler.ts @@ -2,28 +2,20 @@ import { HEADERS, foldLine, compareMsgid, formatCharset, generateHeader } from ' import contentType from 'content-type'; import encoding from 'encoding'; +import {Compiler, GetTextComment, GetTextTranslation, GetTextTranslations, ParserOptions, Translations} from "./types.js"; -/** - * @typedef {import('./types.js').GetTextTranslations} GetTextTranslations - * @typedef {import('./types.js').GetTextTranslation} GetTextTranslation - * @typedef {import('./types.js').GetTextComment} GetTextComment - * @typedef {import('./types.js').Translations} Translations - * @typedef {import('./types.js').ParserOptions} ParserOptions - */ -/** - * @typedef {Partial> & { msgstr?: string | string[] }} PreOutputTranslation - */ +type PreOutputTranslation = Partial> & { msgstr?: string | string[]; }; /** * Exposes general compiler function. Takes a translation * object as a parameter and returns PO object * - * @param {GetTextTranslations} table Translation object - * @param {ParserOptions} [options] Options - * @return {Buffer} The compiled PO object + * @param table Translation object + * @param options Options + * @return The compiled PO object */ -export default function (table, options) { +export default function (table: GetTextTranslations, options: ParserOptions): Buffer { const compiler = new Compiler(table, options); return compiler.compile(); @@ -32,11 +24,11 @@ export default function (table, options) { /** * Takes the header object and converts all headers into the lowercase format * - * @param {Record} headersRaw the headers to prepare - * @returns {Record} the headers in the lowercase format + * @param headersRaw the headers to prepare + * @returns the headers in the lowercase format */ -export function preparePoHeaders (headersRaw) { - return Object.keys(headersRaw).reduce((result, key) => { +export function preparePoHeaders (headersRaw: Record): Record { + return Object.keys(headersRaw).reduce((result: Record, key) => { const lowerKey = key.toLowerCase(); const value = HEADERS.get(lowerKey); @@ -47,17 +39,17 @@ export function preparePoHeaders (headersRaw) { } return result; - }, /** @type {Record} */ ({})); + }, {}); } /** * Creates a PO compiler object. * * @constructor - * @param {GetTextTranslations} [table] Translation table to be compiled - * @param {ParserOptions} [options] Options + * @param table Translation table to be compiled + * @param options Options */ -function Compiler (table, options) { +function Compiler (this: Compiler, table: GetTextTranslations, options: ParserOptions) { this._table = table ?? { headers: {}, charset: undefined, @@ -65,7 +57,7 @@ function Compiler (table, options) { }; this._table.translations = { ...this._table.translations }; - /** @type {ParserOptions} _options The Options object */ + /** _options The Options object */ this._options = { foldLength: 76, escapeCharacters: true, @@ -74,7 +66,6 @@ function Compiler (table, options) { ...options }; - /** @type {Record}} the translation table */ this._table.headers = preparePoHeaders(this._table.headers ?? {}); this._translations = []; @@ -89,7 +80,7 @@ function Compiler (table, options) { * @param {Record} comments A comments object * @return {string} A comment string for the PO file */ -Compiler.prototype._drawComments = function (comments) { +Compiler.prototype._drawComments = function (comments: { [x: string]: string; }): string { /** @var {Record[]} lines The comment lines to be returned */ const lines = []; /** @var {{key: GetTextComment, prefix: string}} type The comment type */ @@ -131,12 +122,12 @@ Compiler.prototype._drawComments = function (comments) { /** * Builds a PO string for a single translation object * - * @param {PreOutputTranslation} block Translation object - * @param {Partial} [override] Properties of this object will override `block` properties - * @param {boolean} [obsolete] Block is obsolete and must be commented out - * @return {string} Translation string for a single object + * @param block Translation object + * @param override Properties of this object will override `block` properties + * @param obsolete Block is obsolete and must be commented out + * @return Translation string for a single object */ -Compiler.prototype._drawBlock = function (block, override = {}, obsolete = false) { +Compiler.prototype._drawBlock = function (block: PreOutputTranslation, override: Partial = {}, obsolete: boolean = false): string { const response = []; const msgctxt = override.msgctxt || block.msgctxt; const msgid = override.msgid || block.msgid; @@ -144,8 +135,7 @@ Compiler.prototype._drawBlock = function (block, override = {}, obsolete = false const msgstrData = override.msgstr || block.msgstr; const msgstr = Array.isArray(msgstrData) ? [...msgstrData] : [msgstrData]; - /** @type {GetTextComment|undefined} */ - const comments = override.comments || block.comments; + const comments: GetTextComment|undefined = override.comments || block.comments; if (comments) { const drawnComments = this._drawComments(comments); if (drawnComments) { @@ -175,18 +165,18 @@ Compiler.prototype._drawBlock = function (block, override = {}, obsolete = false /** * Escapes and joins a key and a value for the PO string * - * @param {string} key Key name - * @param {string} value Key value - * @param {boolean} [obsolete] PO string is obsolete and must be commented out - * @return {string} Joined and escaped key-value pair + * @param key Key name + * @param value Key value + * @param obsolete PO string is obsolete and must be commented out + * @return Joined and escaped key-value pair */ -Compiler.prototype._addPOString = function (key = '', value = '', obsolete = false) { +Compiler.prototype._addPOString = function (key: string = '', value: string = '', obsolete: boolean = false): string { key = key.toString(); if (obsolete) { key = '#~ ' + key; } - let { foldLength, eol, escapeCharacters } = this._options; + let { foldLength, eol, escapeCharacters } = this._options; // escape newlines and quotes if (escapeCharacters) { @@ -249,12 +239,12 @@ Compiler.prototype._handleCharset = function () { /** * Flatten and sort translations object * - * @param {Translations} section Object to be prepared (translations or obsolete) - * @returns {PreOutputTranslation[]|undefined} Prepared array + * @param section Object to be prepared (translations or obsolete) + * @returns Prepared array */ -Compiler.prototype._prepareSection = function (section) { - /** @type {GetTextTranslation[]} response Prepared array */ - let response = []; +Compiler.prototype._prepareSection = function (section: Translations): PreOutputTranslation[] | undefined { + /** response Prepared array */ + let response: GetTextTranslation[] = []; for (const msgctxt in section) { if (typeof section[msgctxt] !== 'object') { @@ -291,22 +281,24 @@ Compiler.prototype._prepareSection = function (section) { * Compiles a translation object into a PO object * * @interface - * @return {Buffer} Compiled a PO object + * @return Compiled a PO object */ -Compiler.prototype.compile = function () { +Compiler.prototype.compile = function (): Buffer { if (!this._table.translations) { throw new Error('No translations found'); } - /** @type {PreOutputTranslation} headerBlock */ - const headerBlock = (this._table.translations[''] && this._table.translations['']['']) || {}; + /** headerBlock */ + const headerBlock: PreOutputTranslation = (this._table.translations[''] && this._table.translations['']['']) || {}; + + /** Translations */ const translations = this._prepareSection(this._table.translations); - let response = /** @type {(PreOutputTranslation|string)[]} */ (/** @type {unknown[]} */ (translations?.map(t => this._drawBlock(t)))); + let response: (PreOutputTranslation|string)[] = (translations?.map((t: unknown[]) => this._drawBlock(t))); if (typeof this._table.obsolete === 'object') { const obsolete = this._prepareSection(this._table.obsolete); if (obsolete && obsolete.length) { - response = response?.concat(obsolete.map(r => this._drawBlock(r, {}, true))); + response = response?.concat(obsolete.map((r: unknown[]) => this._drawBlock(r, {}, true))); } } diff --git a/src/poparser.js b/src/poparser.ts similarity index 75% rename from src/poparser.js rename to src/poparser.ts index 3d209b7..7bb997b 100644 --- a/src/poparser.js +++ b/src/poparser.ts @@ -1,43 +1,31 @@ import encoding from 'encoding'; import { formatCharset, parseHeader, parseNPluralFromHeadersSafely, ParserError } from './shared.js'; -import { Transform } from 'readable-stream'; +import { Transform, TransformOptions } from 'readable-stream'; import util from 'util'; +import {PoParserTransform, GetTextComment, GetTextTranslation, GetTextTranslations, ParserOptions, Translations} from "./types.js"; + +/** Po parser options*/ +type Options = { defaultCharset?: string; validation?: boolean; }; + +/** A single Node object in the PO file */ +export interface Node { + key?: string; + type?: number; + value: string; + quote?: string; + obsolete?: boolean; + comments?: GetTextComment | undefined; +} -/** - * @typedef {import('stream').Stream.Writable} WritableState - * @typedef {import('readable-stream').TransformOptions} TransformOptions - * @typedef {import('./types.js').GetTextTranslations} GetTextTranslations - * @typedef {import('./types.js').GetTextTranslation} GetTextTranslation - * @typedef {import('./types.js').GetTextComment} GetTextComment - * @typedef {import('./types.js').Translations} Translations - * @typedef {import('./types.js').ParserOptions} ParserOptions - */ - -/** - * @typedef {{ defaultCharset?: string, validation?: boolean }} Options Po parser options - */ - -/** - * @typedef {(...args: any[]) => void} DoneCallback - */ - -/** - * @typedef {Object} Node A single Node object in the PO file - * @property {string} [key] - * @property {number} [type] - * @property {string} value - * @property {string} [quote] - * @property {boolean} [obsolete] - * @property {GetTextComment | undefined} [comments] - */ +type DoneCallback = (...args: unknown[]) => void /** * Parses a PO object into translation table * - * @param {string | Buffer} input PO object - * @param {Options} [options] Optional options with defaultCharset and validation + * @param input PO object + * @param [options] Optional options with defaultCharset and validation */ -export function poParse (input, options = {}) { +export function poParse (input: string | Buffer, options: Options = {}) { const parser = new Parser(input, options); return parser.parse(); @@ -46,28 +34,50 @@ export function poParse (input, options = {}) { /** * Parses a PO stream, emits translation table in object mode * - * @param {Options} [options] Optional options with defaultCharset and validation - * @param {TransformOptions} [transformOptions] Optional stream options + * @param [options] Optional options with defaultCharset and validation + * @param [transformOptions] Optional stream options */ -export function poStream (options = {}, transformOptions = {}) { +export function poStream (options: Options = {}, transformOptions: TransformOptions = {}) { return new PoParserTransform(options, transformOptions); } +type Parser = { + _validation: boolean; + _charset: string; + _lex: Node[]; + _escaped: boolean; + _node: Partial; + _state: number; + _lineNumber: number; + _fileContents: string | Buffer; + _handleCharset: (buf: string | Buffer) => string; + states: { + none: number; + header: number; + msgctxt: number; + msgid: number; + msgid_plural: number; + msgstr: number; + msgstr_plural: number; + obsolete: number; + comment: number; + eol: number; + } +} + /** * Creates a PO parser object. * If a PO object is a string, UTF-8 will be used as the charset * - * @param {string | Buffer} fileContents PO object - * @param {Options} options Options with defaultCharset and validation + * @param fileContents PO object + * @param options Options with defaultCharset and validation */ -function Parser (fileContents, { defaultCharset = 'iso-8859-1', validation = false }) { +function Parser (this: Parser, fileContents: string | Buffer, {defaultCharset = 'iso-8859-1', validation = false}: Options) { this._validation = validation; this._charset = defaultCharset; - /** @type {Node[]} Lexed tokens */ this._lex = []; this._escaped = false; - /** @type {Partial} */ this._node = {}; this._state = this.states.none; this._lineNumber = 1; @@ -85,7 +95,7 @@ function Parser (fileContents, { defaultCharset = 'iso-8859-1', validation = fal * * @return {Object} Translation table */ -Parser.prototype.parse = function () { +Parser.prototype.parse = function (): GetTextTranslations { this._lexer(this._fileContents); return this._finalize(this._lex); @@ -94,9 +104,9 @@ Parser.prototype.parse = function () { /** * Detects charset for PO strings from the header * - * @param {string | Buffer} buf Header value + * @param buf Header value */ -Parser.prototype._handleCharset = function (buf = '') { +Parser.prototype._handleCharset = function (buf: string | Buffer = '') { /** @type {string} */ const str = buf.toString(); let pos; @@ -121,10 +131,10 @@ Parser.prototype._handleCharset = function (buf = '') { /** * Converts buffer to string - * @param {string | Buffer} buf Buffer to convert - * @return {string} Converted string + * @param buf Buffer to convert + * @return Converted string */ -Parser.prototype._toString = function (buf) { +Parser.prototype._toString = function (buf: string | Buffer): string { return encoding.convert(buf, 'utf-8', this._charset).toString('utf-8'); }; @@ -160,10 +170,10 @@ Parser.prototype.symbols = { /** * Token parser. Parsed state can be found from this._lex * - * @param {string} chunk String + * @param chunk String * @throws {ParserError} Throws a SyntaxError if the value doesn't match the key names. */ -Parser.prototype._lexer = function (chunk) { +Parser.prototype._lexer = function (chunk: string) { let chr; for (let i = 0, len = chunk.length; i < len; i++) { @@ -259,12 +269,11 @@ Parser.prototype._lexer = function (chunk) { /** * Join multi line strings * - * @param {Node[]} tokens Parsed tokens - * @return {Node[]} Parsed tokens, with multi line strings joined into one + * @param tokens Parsed tokens + * @return Parsed tokens, with multi line strings joined into one */ -Parser.prototype._joinStringValues = function (tokens) { - /** @type {Node[]} */ - const response = []; +Parser.prototype._joinStringValues = function (tokens: Node[]): Node[] { + const response: Node[] = []; let lastNode; for (let i = 0, len = tokens.length; i < len; i++) { @@ -284,18 +293,17 @@ Parser.prototype._joinStringValues = function (tokens) { /** * Parse comments into separate comment blocks * - * @param {Node[]} tokens Parsed tokens + * @param tokens Parsed tokens */ -Parser.prototype._parseComments = function (tokens) { +Parser.prototype._parseComments = function (tokens: Node[]) { for (const node of tokens) { if (!node || node.type !== this.types.comments) { continue; } - /** @type {{ - [key: string]: string[]; - }} */ - const comment = { + const comment: { + [key: string]: string[]; + } = { translator: [], extracted: [], reference: [], @@ -303,8 +311,7 @@ Parser.prototype._parseComments = function (tokens) { previous: [] }; - /** @type {string[]} */ - const lines = (node.value || '').split(/\n/); + const lines: string[] = (node.value || '').split(/\n/); for (const line of lines) { switch (line.charAt(0) || '') { @@ -327,7 +334,7 @@ Parser.prototype._parseComments = function (tokens) { } } - const finalToken = /** @type {Omit & { value: Record}} */ (/** @type {unknown} */ (node)); + const finalToken = node as unknown as Omit & { value: Record}; finalToken.value = {}; @@ -342,14 +349,12 @@ Parser.prototype._parseComments = function (tokens) { /** * Join gettext keys with values * - * @param {(Node & { value?: string })[]} tokens - Parsed tokens containing key-value pairs - * @return {Node[]} - An array of Nodes representing joined tokens + * @param tokens - Parsed tokens containing key-value pairs + * @return An array of Nodes representing joined tokens */ -Parser.prototype._handleKeys = function (tokens) { - /** @type {Node[]} */ - const response = []; - /** @type {Partial & { comments?: string }} */ - let lastNode = {}; +Parser.prototype._handleKeys = function (tokens: (Node & { value?: string })[]): Node[] { + const response: Node[] = []; + let lastNode: Partial & { comments?: string; } = {}; for (let i = 0, len = tokens.length; i < len; i++) { if (tokens[i].type === this.types.key) { @@ -363,7 +368,7 @@ Parser.prototype._handleKeys = function (tokens) { lastNode.comments = tokens[i - 1].value; } lastNode.value = ''; - response.push(/** @type {Node} */ (lastNode)); + response.push((lastNode as Node)); } else if (tokens[i].type === this.types.string && lastNode) { lastNode.value += tokens[i].value; } @@ -378,14 +383,12 @@ Parser.prototype._handleKeys = function (tokens) { * @param {Node[]} tokens Parsed tokens * @return {GetTextTranslation[]} Tokens */ -Parser.prototype._handleValues = function (tokens) { +Parser.prototype._handleValues = function (tokens: Node[]): GetTextTranslation[] { const response = []; - /** @type {GetTextTranslation} Translation object */ - let lastNode = {}; - /** @type {string | undefined} */ - let curContext; - /** @type {GetTextComment | undefined} */ - let curComments; + /** Translation object */ + let lastNode: Partial = {}; + let curContext: string | undefined; + let curComments: GetTextComment | undefined; for (let i = 0, len = tokens.length; i < len; i++) { const tokenKey = tokens[i].key; @@ -448,16 +451,16 @@ Parser.prototype._handleValues = function (tokens) { } } - return response; + return response as GetTextTranslation[]; }; /** * Validate token * - * @param {GetTextTranslation} token Parsed token - * @param {Translations} translations Translation table - * @param {string} msgctxt Message entry context - * @param {number} nplurals Number of expected plural forms + * @param token Parsed token + * @param translations Translation table + * @param msgctxt Message entry context + * @param nplurals Number of expected plural forms * @throws {Error} Will throw an error if token validation fails */ Parser.prototype._validateToken = function ( @@ -465,10 +468,10 @@ Parser.prototype._validateToken = function ( msgid = '', msgid_plural = '', // eslint-disable-line camelcase msgstr = [] - }, - translations, - msgctxt, - nplurals + }: GetTextTranslation, + translations: Translations, + msgctxt: string, + nplurals: number ) { if (msgid in translations[msgctxt]) { throw new SyntaxError(`Duplicate msgid error: entry "${msgid}" in "${msgctxt}" context has already been declared.`); @@ -488,12 +491,11 @@ Parser.prototype._validateToken = function ( * @param {GetTextTranslation[]} tokens Parsed tokens * @return {GetTextTranslations} Translation table */ -Parser.prototype._normalize = function (tokens) { +Parser.prototype._normalize = function (tokens: GetTextTranslation[]): GetTextTranslations { /** * Translation table to be returned - * @type {Omit & Partial> } table */ - const table = { + const table: Omit & Partial> = { charset: this._charset, headers: undefined, translations: {} @@ -501,8 +503,7 @@ Parser.prototype._normalize = function (tokens) { let nplurals = 1; for (let i = 0, len = tokens.length; i < len; i++) { - /** @type {string} */ - const msgctxt = tokens[i].msgctxt || ''; + const msgctxt: string = tokens[i].msgctxt || ''; if (tokens[i].obsolete) { if (!table.obsolete) { @@ -537,16 +538,16 @@ Parser.prototype._normalize = function (tokens) { table.translations[msgctxt][token.msgid] = token; } - return /** @type {GetTextTranslations} */ (table); + return table as GetTextTranslations; }; /** * Converts parsed tokens to a translation table * - * @param {Node[]} tokens Parsed tokens - * @returns {GetTextTranslations} Translation table + * @param tokens Parsed tokens + * @returns Translation table */ -Parser.prototype._finalize = function (tokens) { +Parser.prototype._finalize = function (tokens: Node[]): GetTextTranslations { /** * Translation table */ @@ -562,22 +563,21 @@ Parser.prototype._finalize = function (tokens) { return this._normalize(dataset); }; + + /** * Creates a transform stream for parsing PO input * @constructor - * @this {PoParserTransform & Transform} * - * @param {ParserOptions} options Optional options with defaultCharset and validation - * @param {TransformOptions & {initialTreshold?: number;}} transformOptions Optional stream options + * @param options Optional options with defaultCharset and validation + * @param transformOptions Optional stream options */ -function PoParserTransform (options, transformOptions) { +function PoParserTransform (this: PoParserTransform & Transform, options: ParserOptions, transformOptions: TransformOptions & { initialTreshold?: number; }) { const { initialTreshold, ..._transformOptions } = transformOptions; this.options = options; - /** @type {Parser|false} */ this._parser = false; this._tokens = {}; - /** @type {Buffer[]} */ this._cache = []; this._cacheSize = 0; @@ -592,11 +592,11 @@ util.inherits(PoParserTransform, Transform); /** * Processes a chunk of the input stream - * @param {Buffer} chunk Chunk of the input stream - * @param {string} encoding Encoding of the chunk - * @param {DoneCallback} done Callback to call when the chunk is processed + * @param chunk Chunk of the input stream + * @param encoding Encoding of the chunk + * @param done Callback to call when the chunk is processed */ -PoParserTransform.prototype._transform = function (chunk, encoding, done) { +PoParserTransform.prototype._transform = function (chunk: Buffer, encoding: string, done: DoneCallback) { let i; let len = 0; @@ -662,9 +662,9 @@ PoParserTransform.prototype._transform = function (chunk, encoding, done) { /** * Once all inputs have been processed, emit the parsed translation table as an object * - * @param {DoneCallback} done Callback to call when the chunk is processed + * @param done Callback to call when the chunk is processed */ -PoParserTransform.prototype._flush = function (done) { +PoParserTransform.prototype._flush = function (done: DoneCallback) { let chunk; if (this._cacheSize) { @@ -688,7 +688,7 @@ PoParserTransform.prototype._flush = function (done) { } if (this._parser) { - /** @type {any} */ (this).push(this._parser._finalize(this._parser._lex)); + (this).push(this._parser._finalize(this._parser._lex)); } setImmediate(done); diff --git a/src/shared.js b/src/shared.ts similarity index 61% rename from src/shared.js rename to src/shared.ts index c22cfd6..0fdd1f3 100644 --- a/src/shared.js +++ b/src/shared.ts @@ -1,8 +1,9 @@ // see https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html -/** @type {string} Header name for "Plural-Forms" */ -const PLURAL_FORMS = 'Plural-Forms'; -/** @typedef {Map} Headers Map of header keys to header names */ -export const HEADERS = new Map([ +/** Header name for "Plural-Forms" */ +const PLURAL_FORMS: string = 'Plural-Forms'; + +/** Map of header keys to header names */ +export const HEADERS: Map = new Map([ ['project-id-version', 'Project-Id-Version'], ['report-msgid-bugs-to', 'Report-Msgid-Bugs-To'], ['pot-creation-date', 'POT-Creation-Date'], @@ -15,19 +16,19 @@ export const HEADERS = new Map([ ['plural-forms', PLURAL_FORMS] ]); -const PLURAL_FORM_HEADER_NPLURALS_REGEX = /nplurals\s*=\s*(?\d+)/; +const PLURAL_FORM_HEADER_NPLURALS_REGEX: RegExp = /nplurals\s*=\s*(?\d+)/; /** * Parses a header string into an object of key-value pairs * - * @param {string} str Header string - * @return {Record} An object of key-value pairs + * @param str Header string + * @return An object of key-value pairs */ -export function parseHeader (str = '') { +export function parseHeader (str: string = ''): Record { /** @type {string} Header string */ return str .split('\n') - .reduce((/** @type {Record} */ headers, line) => { + .reduce((headers: Record, line: string) => { const parts = line.split(':'); let key = (parts.shift() || '').trim(); @@ -46,11 +47,11 @@ export function parseHeader (str = '') { /** * Attempts to safely parse 'nplurals" value from "Plural-Forms" header * - * @param {Record} [headers] An object with parsed headers - * @param {number} fallback Fallback value if "Plural-Forms" header is absent - * @returns {number} Parsed result + * @param headers An object with parsed headers + * @param fallback Fallback value if "Plural-Forms" header is absent + * @returns Parsed result */ -export function parseNPluralFromHeadersSafely (headers, fallback = 1) { +export function parseNPluralFromHeadersSafely (headers: Record, fallback: number = 1): number { const pluralForms = headers ? headers[PLURAL_FORMS] : false; if (!pluralForms) { @@ -67,10 +68,10 @@ export function parseNPluralFromHeadersSafely (headers, fallback = 1) { /** * Joins a header object of key value pairs into a header string * - * @param {Record} header Object of key value pairs - * @return {string} An object of key-value pairs + * @param header Object of key value pairs + * @return An object of key-value pairs */ -export function generateHeader (header = {}) { +export function generateHeader (header: Record = {}): string { const keys = Object.keys(header) .filter(key => !!key); @@ -87,11 +88,11 @@ export function generateHeader (header = {}) { /** * Normalizes charset name. Converts utf8 to utf-8, WIN1257 to windows-1257 etc. * - * @param {string} charset Charset name - * @param {string} defaultCharset Default charset name, defaults to 'iso-8859-1' - * @return {string} Normalized charset name + * @param charset Charset name + * @param defaultCharset Default charset name, defaults to 'iso-8859-1' + * @return Normalized charset name */ -export function formatCharset (charset = 'iso-8859-1', defaultCharset = 'iso-8859-1') { +export function formatCharset (charset: string = 'iso-8859-1', defaultCharset: string = 'iso-8859-1'): string { return charset.toString() .toLowerCase() .replace(/^utf[-_]?(\d+)$/, 'utf-$1') @@ -105,11 +106,11 @@ export function formatCharset (charset = 'iso-8859-1', defaultCharset = 'iso-885 /** * Folds long lines according to PO format * - * @param {string} str PO formatted string to be folded - * @param {number} [maxLen=76] Maximum allowed length for folded lines - * @return {string[]} An array of lines + * @param str PO formatted string to be folded + * @param maxLen Maximum allowed length for folded lines + * @return An array of lines */ -export function foldLine (str, maxLen = 76) { +export function foldLine (str: string, maxLen: number = 76): string[] { const lines = []; const len = str.length; let curLine = ''; @@ -117,15 +118,12 @@ export function foldLine (str, maxLen = 76) { let match; while (pos < len) { - let escaped = false; - curLine = str.substring(pos, pos + maxLen); // ensure that the line never ends with a partial escaping // make longer lines if needed - if (curLine.endsWith('\\') && pos + curLine.length < len) { - escaped = true; - curLine += str.charAt(pos + curLine.length); // Append the next character + while (curLine.endsWith('\\') && pos + curLine.length < len) { + curLine += str.charAt(pos + curLine.length + 1); // Append the next character } // ensure that if possible, line breaks are done at reasonable places @@ -137,7 +135,7 @@ export function foldLine (str, maxLen = 76) { if ((match = /.*\s+/.exec(curLine)) && /\S/.test(match[0])) { // use everything before and including the last white space character (if anything) curLine = match[0]; - } else if (!escaped && (match = /.*[\x21-\x2f0-9\x5b-\x60\x7b-\x7e]+/.exec(curLine)) && /[^\x21-\x2f0-9\x5b-\x60\x7b-\x7e]/.test(match[0])) { + } else if ((match = /.*[\x21-\x2f0-9\x5b-\x60\x7b-\x7e]+/.exec(curLine)) && /[^\x21-\x2f0-9\x5b-\x60\x7b-\x7e]/.test(match[0])) { // use everything before and including the last "special" character (if anything) curLine = match[0]; } @@ -153,12 +151,11 @@ export function foldLine (str, maxLen = 76) { /** * Comparator function for comparing msgid * - * @template {Buffer|string} T - * @param {{msgid: T}} left with msgid prev - * @param {{msgid: T}} right with msgid next - * @returns {number} comparator index + * @param left with msgid prev + * @param right with msgid next + * @returns comparator index */ -export function compareMsgid ({ msgid: left }, { msgid: right }) { +export function compareMsgid ({msgid: left}: { msgid: T; }, {msgid: right}: { msgid: T; }): number { if (left < right) { return -1; } @@ -174,11 +171,12 @@ export function compareMsgid ({ msgid: left }, { msgid: right }) { * Custom SyntaxError subclass that includes the lineNumber property. */ export class ParserError extends SyntaxError { + lineNumber: number; /** - * @param {string} message - Error message. - * @param {number} lineNumber - Line number where the error occurred. + * @param message - Error message. + * @param lineNumber - Line number where the error occurred. */ - constructor (message, lineNumber) { + constructor (message: string, lineNumber: number) { super(message); this.lineNumber = lineNumber; } diff --git a/src/types.js b/src/types.js deleted file mode 100644 index 450d778..0000000 --- a/src/types.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Represents a GetText comment. - * @typedef {Object} GetTextComment - * @property {string} [translator] Translator information. - * @property {string} [reference] Reference information. - * @property {string} [extracted] Extracted comments. - * @property {string} [flag] Flags. - * @property {string} [previous] Previous string. - */ - -/** - * Represents a GetText translation. - * @typedef {Object} GetTextTranslation - * @property {string} [msgctxt] Context of the message. - * @property {string} msgid The singular message ID. - * @property {string} [msgid_plural] The plural message ID. - * @property {string[]} msgstr Array of translated strings. - * @property {GetTextComment} [comments] Comments associated with the translation. - * @property {boolean} [obsolete] Whether the translation is obsolete. - */ - -/** - * @typedef {Record>} Translations The translations index. - */ - -/** - * Represents GetText translations. - * @typedef {Object} GetTextTranslations - * @property {string|undefined} charset Character set. - * @property {Record} headers Headers. - * @property {Translations} [obsolete] Obsolete messages. - * @property {Translations} translations Translations. - */ - -/** - * Options for the parser. - * @typedef {Object} ParserOptions - * @property {string} [defaultCharset] Default character set. - * @property {boolean} [validation] Whether to perform validation. - * @property {number} [foldLength] the fold length. - * @property {boolean} [escapeCharacters] Whether to escape characters. - * @property {boolean} [sort] Whether to sort messages. - * @property {string} [eol] End of line character. - */ - -/** - * @typedef {('writeUInt32LE'|'writeUInt32BE')} WriteFunc Type definition for write functions. - */ - -/** - * @typedef {('readUInt32LE'|'readUInt32BE')} ReadFunc Type definition for read functions. - */ diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..9d4a31b --- /dev/null +++ b/src/types.ts @@ -0,0 +1,111 @@ +import {Transform} from "readable-stream"; + +/** + * Represents a GetText comment. + */ +export interface GetTextComment { + translator?: string; + reference?: string; + extracted?: string; + flag?: string; + previous?: string; +} + +/** + * Represents a GetText translation. + */ +export interface GetTextTranslation { + msgctxt?: string; + msgid: string; + msgid_plural?: string; + msgstr: string[]; + comments?: GetTextComment; + obsolete?: boolean; +} + +/** + * The translation index. + */ +export type Translations = Record> + +/** + * Represents GetText translations. + */ +export interface GetTextTranslations { + charset: string | undefined; + headers: Record; + obsolete?: Translations; + translations: Translations; +} + +/** + * Options for the parser. + */ +export type ParserOptions = { + defaultCharset?: string; + validation?: boolean; + foldLength?: number; + escapeCharacters?: boolean; + sort?: boolean; + eol?: string; +} + +/** + * Type definition for write functions. + */ +export type WriteFunc = 'writeUInt32LE' | 'writeUInt32BE'; + +/** + * Type definition for read functions. + */ +export type ReadFunc = 'readUInt32LE' | 'readUInt32BE'; + + +/** The size of the MO object */ +export type Size = { + msgid: number, + msgstr: number, + total: number +} + +/** The translation object as a buffer */ +export type TranslationBuffers = { + msgid: Buffer, + msgstr: Buffer +} + +export type Compiler = { + _options: ParserOptions; + _table: GetTextTranslations, + _translations: TranslationBuffers[], + _writeFunc: WriteFunc, + _handleCharset: () => void, + _generateList: () => TranslationBuffers[], + _build: (list: TranslationBuffers[], size: Size) => Buffer, + compile: () => Buffer, + /** + * Magic bytes for the generated binary data + * MAGIC file header magic value of mo file + */ + MAGIC: number, +} + +export type Parser = { + _validation: boolean; + _charset: string; + _lex: any[]; + _escaped: boolean; + _node: any; + _state: any; + _lineNumber: number; + _fileContents: string | Buffer; +} + +export type PoParserTransform = { + options: ParserOptions, + initialTreshold?: number, + _parser?: Parser|false, + _tokens?: {}, + _cache?: Buffer[], + _cacheSize?: number +}; diff --git a/tsconfig.json b/tsconfig.json index b82537e..2d46a1e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "moduleResolution": "Node16", "target": "ES2018", "lib": [ - "ES2018" + "ES2018", + "dom" ], // Strict mode "strict": true, @@ -28,7 +29,7 @@ "typeRoots": [ "./types", "./node_modules/@types" - ], + ] }, "include": [ "src/**/*",