ifc-language-server/node_modules/@stylistic/eslint-plugin/dist/rules/jsx-curly-spacing.js
Ryan Schultz 8afacf268a Implemented a working Language Server Protocol (LSP) for IFC files with:
- Hover provider showing entity information and type
- Go-to-definition (F12) for entity references
- Basic IFC file validation (ISO-10303-21 header check)
- Entity parsing with regex-based detection
- Proper CommonJS module system (avoiding ES module issues)

This replaces the broken baseline from ifc-developer-tools which had:
- Non-functional ES module configuration
- Circular dependency issues
- Parser crashes
- Non-working PositionVisitor

Built on Microsoft's LSP example template for a clean, maintainable foundation.

Next: Add hierarchical entity dependency tree in hover tooltip."
2025-12-07 10:20:07 -06:00

305 lines
10 KiB
JavaScript

'use strict';
var utils = require('../utils.js');
require('eslint-visitor-keys');
require('espree');
require('estraverse');
const SPACING = {
always: "always",
never: "never"
};
const SPACING_VALUES = [SPACING.always, SPACING.never];
const messages = {
noNewlineAfter: "There should be no newline after '{{token}}'",
noNewlineBefore: "There should be no newline before '{{token}}'",
noSpaceAfter: "There should be no space after '{{token}}'",
noSpaceBefore: "There should be no space before '{{token}}'",
spaceNeededAfter: "A space is required after '{{token}}'",
spaceNeededBefore: "A space is required before '{{token}}'"
};
var jsxCurlySpacing = utils.createRule({
name: "jsx-curly-spacing",
package: "jsx",
meta: {
type: "layout",
docs: {
description: "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions"
},
fixable: "code",
messages,
schema: {
definitions: {
basicConfig: {
type: "object",
properties: {
when: {
type: "string",
enum: SPACING_VALUES
},
allowMultiline: {
type: "boolean"
},
spacing: {
type: "object",
properties: {
objectLiterals: {
type: "string",
enum: SPACING_VALUES
}
}
}
}
},
basicConfigOrBoolean: {
anyOf: [{
$ref: "#/definitions/basicConfig"
}, {
type: "boolean"
}]
}
},
type: "array",
items: [{
anyOf: [{
allOf: [{
$ref: "#/definitions/basicConfig"
}, {
type: "object",
properties: {
attributes: {
$ref: "#/definitions/basicConfigOrBoolean"
},
children: {
$ref: "#/definitions/basicConfigOrBoolean"
}
}
}]
}, {
type: "string",
enum: SPACING_VALUES
}]
}, {
type: "object",
properties: {
allowMultiline: {
type: "boolean"
},
spacing: {
type: "object",
properties: {
objectLiterals: {
type: "string",
enum: SPACING_VALUES
}
}
}
},
additionalProperties: false
}]
}
},
create(context) {
function normalizeConfig(configOrTrue, defaults, lastPass = false) {
const config = configOrTrue === true ? {} : configOrTrue;
const when = config.when || defaults.when;
const allowMultiline = "allowMultiline" in config ? config.allowMultiline : defaults.allowMultiline;
const spacing = config.spacing || {};
let objectLiteralSpaces = spacing.objectLiterals || defaults.objectLiteralSpaces;
if (lastPass) {
objectLiteralSpaces = objectLiteralSpaces || when;
}
return {
when,
allowMultiline,
objectLiteralSpaces
};
}
const DEFAULT_WHEN = SPACING.never;
const DEFAULT_ALLOW_MULTILINE = true;
const DEFAULT_ATTRIBUTES = true;
const DEFAULT_CHILDREN = false;
let originalConfig = context.options[0] || {};
if (SPACING_VALUES.includes(originalConfig))
originalConfig = Object.assign({ when: context.options[0] }, context.options[1]);
originalConfig = originalConfig;
const defaultConfig = normalizeConfig(originalConfig, {
when: DEFAULT_WHEN,
allowMultiline: DEFAULT_ALLOW_MULTILINE
});
const attributes = "attributes" in originalConfig ? originalConfig.attributes : DEFAULT_ATTRIBUTES;
const attributesConfig = attributes ? normalizeConfig(attributes, defaultConfig, true) : null;
const children = "children" in originalConfig ? originalConfig.children : DEFAULT_CHILDREN;
const childrenConfig = children ? normalizeConfig(children, defaultConfig, true) : null;
function isMultiline(left, right) {
return left.loc.end.line !== right.loc.start.line;
}
function fixByTrimmingWhitespace(fixer, fromLoc, toLoc, mode, spacing = "") {
let replacementText = context.sourceCode.text.slice(fromLoc, toLoc);
if (mode === "start")
replacementText = replacementText.replace(/^\s+/gm, "");
else
replacementText = replacementText.replace(/\s+$/gm, "");
if (spacing === SPACING.always) {
if (mode === "start")
replacementText += " ";
else
replacementText = ` ${replacementText}`;
}
return fixer.replaceTextRange([fromLoc, toLoc], replacementText);
}
function reportNoBeginningNewline(node, token, spacing) {
context.report({
node,
loc: token.loc.start,
messageId: "noNewlineAfter",
data: {
token: token.value
},
fix(fixer) {
const nextToken = context.sourceCode.getTokenAfter(token);
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], "start", spacing);
}
});
}
function reportNoEndingNewline(node, token, spacing) {
context.report({
node,
loc: token.loc.start,
messageId: "noNewlineBefore",
data: {
token: token.value
},
fix(fixer) {
const previousToken = context.sourceCode.getTokenBefore(token);
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], "end", spacing);
}
});
}
function reportNoBeginningSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "noSpaceAfter",
data: {
token: token.value
},
fix(fixer) {
const sourceCode = context.sourceCode;
const nextToken = sourceCode.getTokenAfter(token);
const nextComment = sourceCode.getCommentsAfter(token);
if (nextComment.length > 0)
return fixByTrimmingWhitespace(fixer, token.range[1], Math.min(nextToken.range[0], nextComment[0].range[0]), "start");
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], "start");
}
});
}
function reportNoEndingSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "noSpaceBefore",
data: {
token: token.value
},
fix(fixer) {
const sourceCode = context.sourceCode;
const previousToken = sourceCode.getTokenBefore(token);
const previousComment = sourceCode.getCommentsBefore(token);
if (previousComment.length > 0)
return fixByTrimmingWhitespace(fixer, Math.max(previousToken.range[1], previousComment[0].range[1]), token.range[0], "end");
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], "end");
}
});
}
function reportRequiredBeginningSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "spaceNeededAfter",
data: {
token: token.value
},
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
function reportRequiredEndingSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "spaceNeededBefore",
data: {
token: token.value
},
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
function validateBraceSpacing(node) {
let config;
switch (node.parent?.type) {
case "JSXAttribute":
case "JSXOpeningElement":
config = attributesConfig;
break;
case "JSXElement":
case "JSXFragment":
config = childrenConfig;
break;
default:
return;
}
if (config === null)
return;
const sourceCode = context.sourceCode;
const first = sourceCode.getFirstToken(node);
const last = sourceCode.getLastToken(node);
let second = sourceCode.getTokenAfter(first, { includeComments: true });
let penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
if (!second) {
second = sourceCode.getTokenAfter(first);
const leadingComments = sourceCode.getCommentsBefore(second);
second = leadingComments ? leadingComments[0] : second;
}
if (!penultimate) {
penultimate = sourceCode.getTokenBefore(last);
const trailingComments = sourceCode.getCommentsAfter(penultimate);
penultimate = trailingComments ? trailingComments[trailingComments.length - 1] : penultimate;
}
const isObjectLiteral = first.value === second.value;
const spacing = isObjectLiteral ? config.objectLiteralSpaces : config.when;
if (spacing === SPACING.always) {
if (!sourceCode.isSpaceBetweenTokens(first, second))
reportRequiredBeginningSpace(node, first);
else if (!config.allowMultiline && isMultiline(first, second))
reportNoBeginningNewline(node, first, spacing);
if (!sourceCode.isSpaceBetweenTokens(penultimate, last))
reportRequiredEndingSpace(node, last);
else if (!config.allowMultiline && isMultiline(penultimate, last))
reportNoEndingNewline(node, last, spacing);
} else if (spacing === SPACING.never) {
if (isMultiline(first, second)) {
if (!config.allowMultiline)
reportNoBeginningNewline(node, first, spacing);
} else if (sourceCode.isSpaceBetweenTokens(first, second)) {
reportNoBeginningSpace(node, first);
}
if (isMultiline(penultimate, last)) {
if (!config.allowMultiline)
reportNoEndingNewline(node, last, spacing);
} else if (sourceCode.isSpaceBetweenTokens(penultimate, last)) {
reportNoEndingSpace(node, last);
}
}
}
return {
JSXExpressionContainer: validateBraceSpacing,
JSXSpreadAttribute: validateBraceSpacing
};
}
});
module.exports = jsxCurlySpacing;