From 0afdea18fe1af3282827d74aaff28785f450e34f Mon Sep 17 00:00:00 2001 From: DenisPower1 Date: Wed, 16 Oct 2024 19:14:06 +0100 Subject: [PATCH] Upgrade to v1.3.0 --- package.json | 2 +- src/errors.ts | 43 ++++++++++ src/extension.ts | 153 +++++++++++++++++++++--------------- src/hop/helpers.ts | 39 +++++++++ src/hop/index.ts | 192 +++++++++++++++++++++++++++++++++++++++++++++ src/hop/types.ts | 30 +++++++ tsconfig.json | 10 ++- 7 files changed, 402 insertions(+), 67 deletions(-) create mode 100644 src/errors.ts create mode 100644 src/hop/helpers.ts create mode 100644 src/hop/index.ts create mode 100644 src/hop/types.ts diff --git a/package.json b/package.json index b895cf6..cd3aee4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/interjs/inter-intellisense.git" }, "module": "esnext", - "version": "1.2.1", + "version": "1.3.0", "engines": { "vscode": "^1.57.1" }, diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..531431e --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,43 @@ +export function runUnregisteredConditionalPropError(prop: string): string { + + return ` ${prop} is not a registered conditional property. Register it like: + + + ` + +} + +export function runIllegalValueError(value: string): string { + + return `${value} seems to be a reference's name, avoid this please!` + +} + +export function runHasNoreThanOneConditionalAttrsError(tagName: string | undefined): string { + + + return ` This ${tagName} tag has more than one conditional attribute, it's forbidden.` + +} + +export function runInvalidConditionalAttrNameError(attr: string): string { + + return ` Attribute ${attr} is invalid here, because it has an empty value, assign a value to it.` + +} + +export function runInvalidElseValueWarning(value: string): string { + + return ` Remove ${value}, _else is not supposed to have a value.` + + +} + +export function runUnregisteredRefName(refName: string): string { + + return `${refName} is an unregistered reference's name. Register it like this: + + + ` + +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 875af07..9b1ee15 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,14 +1,17 @@ import * as vscode from "vscode"; import { commentsParser, parserInfoInterface } from "./htmlcommentsparser"; +import { parseHTML } from "./hop/index" +import { runHasNoreThanOneConditionalAttrsError, runIllegalValueError, runInvalidConditionalAttrNameError, runInvalidElseValueWarning, runUnregisteredConditionalPropError, runUnregisteredRefName } from "./errors" -const extensionVersion: string = "1.2.1"; +const extensionVersion: string = "1.3.0"; const openTagRegExp = /<(:?[A-Z]+)>/gi; interface configI { - text: string; + start: number; end: number; justWarning?: boolean; type: registeredT; + errorMessage: string } type registeredT = "ref" | "conditional"; @@ -18,7 +21,7 @@ function getRegistered( type: registeredT ): parserInfoInterface | undefined { const registered = new commentsParser().parse(document); - + let returnValue; if (registered[0]?.type == type) returnValue = registered[0]; @@ -41,7 +44,7 @@ function findUnregisteredRef( const text = ref.replace("{", "").replace("}", "").trim(); const start = documentContent.indexOf(ref) + 1; const position = document.positionAt(start); - + const refSet = new Set(registered ? registered.content : []); @@ -49,11 +52,12 @@ function findUnregisteredRef( else if (outOfCompletionArea(document, position)) continue; const config: configI = { - text: text, + start: start, end: documentContent.indexOf(ref) + ref.length, justWarning: true, type: "ref", + errorMessage: runUnregisteredRefName(text) }; unregistered.push(config); @@ -62,45 +66,93 @@ function findUnregisteredRef( return unregistered; } -function findUnregisteredConditionalProps(documentContent: string): configI[] | undefined { - const attrsReg: RegExp = - /_if="(:?\s)*(:?[A-Z]+)(:?\s)*"|_elseIf="(:?\s)*(:?[A-Z]+)(:?\s)*"|_ifNot="(:?\s)*(:?[A-Z]+)(:?\s)*"|_else="(:?\s)*(:?[A-Z]+)(:?\s)*"/gi; - const allAttrs = documentContent.match(attrsReg) || []; - const unregistered = []; - const registered = getRegistered(documentContent, "conditional"); - if (!registered) return; - for (const attr of allAttrs) { - let theConditionalAttrsLength: number = 0; - let attrName: string = ""; +function generateConditionalConfig(start: number, end: number, error: string, warning?: boolean): configI { + + return { + type: "conditional", + start: start, + end: end, + errorMessage: error, + justWarning: warning + } + +} + +function findErrorInConditionalRendering(documentContent: string): configI[] { + + const domLikeObject = parseHTML(documentContent); + const registeredC = getRegistered(documentContent, "conditional") + const conditionalProps = new Set(registeredC ? registeredC.content : []); + const conditionalAttrsNames = new Set(["_if", "_elseIf", "_else", "_ifNot"]) + const errorsCollection = [] + + for (const element of domLikeObject) { + + let conditionalAttrsCounter = 0; + + + if (Array.isArray(element.attrs) && element.attrs.length > 0) { + + if (conditionalProps.size == 0) break; + + for (const attr of element.attrs) { + + const { name, nameStart, nameEnd, value, valueSart, valueEnd } = attr + const { tag, tagStart, tagEnd } = element; + const refReg = /\{(:?\s+)*(:?[A-Z]+)(:?\s+)*\}/gi; + + if (conditionalAttrsNames.has(name)) { + conditionalAttrsCounter++; + + if (conditionalAttrsCounter > 1) { + errorsCollection.push( + generateConditionalConfig(tagStart, tagEnd, runHasNoreThanOneConditionalAttrsError(tag)) + ) + break + } + else if (name !== "_else" && refReg.test(value)) { + const refName = value.replace("{", "").replace("}", "").trim(); + errorsCollection.push(generateConditionalConfig(valueSart, valueEnd, runIllegalValueError(refName))); + break; + + } + else if (name == "_else" && value.trim().length > 0) { + errorsCollection.push(generateConditionalConfig(valueSart, valueEnd, runInvalidElseValueWarning(value), true)); + break; + } + else if (name !== "_else" && value.trim().length == 0) { + errorsCollection.push(generateConditionalConfig(nameStart, nameEnd, runInvalidConditionalAttrNameError(name))); + break; + } + else if (!conditionalProps.has(value)) { + errorsCollection.push(generateConditionalConfig(valueSart, valueEnd, runUnregisteredConditionalPropError(value))); + break + + } + + } + + + + } + + } - const text = attr - .replace(/_if="|_elseIf="|_ifNot="|_else="/, (prop) => { - theConditionalAttrsLength = prop.length; - attrName = prop.replace('="', ""); - return ""; - }) - .replace('"', "") - .trim(); - const conditionalSet = new Set(registered?.content); - if (conditionalSet.has(text) && attrName !== "_else") continue; - const config: configI = { - text: text, - start: documentContent.indexOf(attr) + theConditionalAttrsLength, - end: documentContent.indexOf(attr) + attr.length, - justWarning: attrName == "_else", - type: "conditional", - }; - unregistered.push(config); } - return unregistered; + return errorsCollection; + + + + } + function getHoveredToken(cursorPosition: number, lineText: string): string { let body: string = ""; @@ -183,21 +235,6 @@ function outOfCompletionArea( return noCompletion; } -function isConditionalAttr( - document: vscode.TextDocument, - position: vscode.Position -): boolean { - const lineText = document.lineAt(position).text; - const conditionalAttrRegExp = /_if=""|_elseIf=""|_ifNot=""/g; - let result: boolean = false; - const tagWithAttrsRegExp = /<(?:[\s\S]+)>/g; - - if (tagWithAttrsRegExp.test(lineText)) { - if (conditionalAttrRegExp.test(lineText)) result = true; - } - - return result; -} function isReferenceSuggestionRequest( textLine: string, @@ -382,24 +419,11 @@ export function activate(context: vscode.ExtensionContext) { const start = toPosition(prop.start); const end = toPosition(prop.end); const diagnosticSeverity = vscode.DiagnosticSeverity; - const notRegisteredPropError = !prop.justWarning - ? ` - ${prop.text} is not a registered conditional property. Register it like: - - - - ` - : "Nothing is wrong here, but _else does not expect a value!"; - const notRegisteredRefWarning = ` - ${prop.text} is not a registered reference's name. Register it like: - - - `; const diagnostic = new vscode.Diagnostic( new vscode.Range(start, end), - prop.type == "ref" ? notRegisteredRefWarning : notRegisteredPropError, + prop.errorMessage, prop.justWarning ? diagnosticSeverity.Warning : diagnosticSeverity.Error ); @@ -411,8 +435,7 @@ export function activate(context: vscode.ExtensionContext) { const documentContent = document.getText(); const toPosition = (offset: number) => document.positionAt(offset); const diagnostics: vscode.Diagnostic[] = []; - const unregisteredConditional = - findUnregisteredConditionalProps(documentContent); + const unregisteredConditional = findErrorInConditionalRendering(documentContent); const unregisteredRef = findUnregisteredRef(documentContent, document); if (unregisteredConditional) runDiagnosticLoop(unregisteredConditional, diagnostics, toPosition); diff --git a/src/hop/helpers.ts b/src/hop/helpers.ts new file mode 100644 index 0000000..9f2a3ab --- /dev/null +++ b/src/hop/helpers.ts @@ -0,0 +1,39 @@ +import type { attrsI, htmlStructureI } from "./types"; + +export function isACharacter(arg: string): boolean { + return /^[A-z]|\d$/i.test(arg); +} + +export function isASpace(arg: string): boolean { + return /^\s$/.test(arg); +} + +export function mergeAttr(attribute: attrsI, html: htmlStructureI, attrValue: string): void { + + attribute.value = attrValue + + html.attrs.push( + Object.assign({}, attribute) + ); + + attribute.name = ""; + attribute.value = ""; + +} + +export function hasProps(obj: Object): boolean { + + return Object.keys(obj).length > 0; + +} + +export function resetElementProps(element: htmlStructureI): void { + + element.tag = ""; + element.type = null; + element.tagStart = 0; + element.tagEnd = 0; + element.attrs = []; + + +} \ No newline at end of file diff --git a/src/hop/index.ts b/src/hop/index.ts new file mode 100644 index 0000000..52f700f --- /dev/null +++ b/src/hop/index.ts @@ -0,0 +1,192 @@ +import { isASpace, isACharacter, mergeAttr, resetElementProps } from "./helpers" +import type { attrsI, htmlStructureI, parserSettingI } from "./types" + +function runParsing(token: string, element: htmlStructureI, attribute: attrsI, parserSetting: parserSettingI, html: htmlStructureI[], index: number) { + const { + tagName, + attrName, + attrValue, + parsingTag, + parsingAttrs, + nowLookingAtValue, + } = parserSetting + + if (!parsingTag && token == "<") { + parserSetting.parsingTag = true; + element.tagStart = index; + if (parserSetting.tagName) parserSetting.tagName = ""; + + } + + else if (parsingTag && tagName.trim().length == 0 && isACharacter(token)) { + + + if (isACharacter(token)) parserSetting.tagName += token; + + } + + else if (parsingAttrs && token == ">") { + if (parserSetting.attrName && !attribute.name) { + attribute.name = attrName; + parserSetting.attrName = ""; + } + + if (attribute.name) { + mergeAttr(attribute, element, attrValue) + parserSetting.attrValue = ""; + parserSetting.attrName = ""; + parserSetting.nowLookingAtValue = false; + parserSetting.parsingAttrs = false; + } + + element.tagEnd = index; + + html.push( + Object.assign({}, element) + ); + + resetElementProps(element) + + parserSetting.parsingAttrs = false; + + } + + else if (parsingTag && token == ">") { + + + if (parserSetting.tagName && !element.tag) { + element.type = "Tag"; + element.tag = tagName; + parserSetting.parsingTag = false; + } + + element.tagEnd = index; + + html.push( + Object.assign({}, element) + ); + + resetElementProps(element) + + + parserSetting.parsingAttrs = false; + + } + + + else if (parsingAttrs && attrName.trim().length > 0 && isASpace(token) && !nowLookingAtValue) { + //It's not necessary to have the = to be considered + // as a valid attribute + // for instance + + + attribute.name = attrName; + attribute.nameEnd = index; + mergeAttr(attribute, element, ""); + + parserSetting.attrName = ""; + + + } + + else if (parsingTag && tagName && isACharacter(token)) { + parserSetting.tagName += token; + + } + + else if (parsingTag && tagName && isASpace(token)) { + // Okay, we have found the complete tag's name + + parserSetting.parsingTag = false; + parserSetting.parsingAttrs = true; + element.tag = tagName; + element.type = "Tag"; + parserSetting.tagName = ""; + } + else if (parsingAttrs && !attrName && token.trim().length > 0 && !nowLookingAtValue || parsingAttrs && attrName && token && token !== "=") { + parserSetting.attrName += token + if (!attrName) attribute.nameStart = index; + } + + + else if (parsingAttrs && attrName && token == "=") { + //Okay now we have the attribute's name. + + attribute.name = attrName; + attribute.nameEnd = index; + parserSetting.attrName = ""; + parserSetting.nowLookingAtValue = true; + + } + + else if (parsingAttrs && nowLookingAtValue && !attrValue && (token == "'" || token == '"')) { + // Okay we have to skip the attribute's value opening + return; + + } + + else if (parsingAttrs && nowLookingAtValue && attrValue && (token == "'" || token == '"')) { + attribute.valueEnd = index; + mergeAttr(attribute, element, attrValue) + parserSetting.attrValue = ""; + parserSetting.nowLookingAtValue = false; + + } + + else if (parsingAttrs && nowLookingAtValue && !attrValue && token || parsingAttrs && nowLookingAtValue && attrValue && token) { + if (!attrValue) attribute.valueSart = index; + parserSetting.attrValue += token; + + } + +} + +export function parseHTML(content: string): htmlStructureI[] { + + + const html: htmlStructureI[] = []; + const element: htmlStructureI = { + type: null, + tag: "", + tagStart: 0, + tagEnd: 0, + attrs: [] + + } + + const attribute: attrsI = { + name: "", + value: "", + nameStart: 0, + nameEnd: 0, + valueSart: 0, + valueEnd: 0 + } + + + const parserSetting: parserSettingI = { + tagName: "", + attrName: "", + attrValue: "", + nowLookingAtValue: false, + parsingTag: false, + parsingAttrs: false, + parsingNested: false + } + + for (let i = 0; i < content.length; i++) { + const token = content[i]; + const nextToken = content[i + 1]; + + if (token == "<" && (nextToken == "/" || nextToken == "!")) continue; + + + runParsing(token, element, attribute, parserSetting, html, i); + + + } + + return html; + +} + diff --git a/src/hop/types.ts b/src/hop/types.ts new file mode 100644 index 0000000..d169176 --- /dev/null +++ b/src/hop/types.ts @@ -0,0 +1,30 @@ +type s = string; + +export interface attrsI { + name: T, + value: T, + nameStart: number, + nameEnd: number, + valueSart: number, + valueEnd: number + +} + +export interface htmlStructureI { + type: "Tag" | "Text" | null + tag: s, + tagStart: number, + tagEnd: number, + attrs: attrsI[] + +} + +export interface parserSettingI { + tagName: s, + attrName: s, + attrValue: s, + nowLookingAtValue: boolean, + parsingTag: boolean, + parsingAttrs: boolean, + parsingNested: boolean +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 2ef8180..40cd237 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,9 +12,17 @@ "strictFunctionTypes": true, "strictPropertyInitialization": true, "strictNullChecks": true, + "noUnusedLocals": true, "outDir": "./out", }, - "files": ["./src/extension.ts", "src/htmlcommentsparser.ts"], + "files": [ + "src/extension.ts", + "src/htmlcommentsparser.ts", + "src/errors.ts", + "src/hop/index.ts", + "src/hop/helpers.ts", + "src/hop/types.ts" + ], } \ No newline at end of file