ifc-language-server/node_modules/@stylistic/eslint-plugin/dist/rules/member-delimiter-style.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

235 lines
6.8 KiB
JavaScript

'use strict';
var utils = require('../utils.js');
var utils$1 = require('@typescript-eslint/utils');
require('eslint-visitor-keys');
require('espree');
require('estraverse');
function isLastTokenEndOfLine(token, line) {
const positionInLine = token.loc.start.column;
return positionInLine === line.length - 1;
}
function isCommentsEndOfLine(token, comments, line) {
if (!comments)
return false;
if (comments.loc.end.line > token.loc.end.line)
return true;
const positionInLine = comments.loc.end.column;
return positionInLine === line.length;
}
function makeFixFunction({
optsNone,
optsSemi,
lastToken,
commentsAfterLastToken,
missingDelimiter,
lastTokenLine,
isSingleLine
}) {
if (optsNone && !isLastTokenEndOfLine(lastToken, lastTokenLine) && !isCommentsEndOfLine(lastToken, commentsAfterLastToken, lastTokenLine) && !isSingleLine) {
return null;
}
return (fixer) => {
if (optsNone) {
return fixer.remove(lastToken);
}
const token = optsSemi ? ";" : ",";
if (missingDelimiter) {
return fixer.insertTextAfter(lastToken, token);
}
return fixer.replaceText(lastToken, token);
};
}
const BASE_SCHEMA = {
type: "object",
properties: {
multiline: {
type: "object",
properties: {
delimiter: { $ref: "#/items/0/$defs/multiLineOption" },
requireLast: { type: "boolean" }
},
additionalProperties: false
},
singleline: {
type: "object",
properties: {
delimiter: { $ref: "#/items/0/$defs/singleLineOption" },
requireLast: { type: "boolean" }
},
additionalProperties: false
}
},
additionalProperties: false
};
var memberDelimiterStyle = utils.createRule({
name: "member-delimiter-style",
package: "ts",
meta: {
type: "layout",
docs: {
description: "Require a specific member delimiter style for interfaces and type literals"
},
fixable: "whitespace",
messages: {
unexpectedComma: "Unexpected separator (,).",
unexpectedSemi: "Unexpected separator (;).",
expectedComma: "Expected a comma.",
expectedSemi: "Expected a semicolon."
},
schema: [
{
$defs: {
multiLineOption: {
type: "string",
enum: ["none", "semi", "comma"]
},
// note can't have "none" for single line delimiter as it's invalid syntax
singleLineOption: {
type: "string",
enum: ["semi", "comma"]
},
// note - need to define this last as it references the enums
delimiterConfig: BASE_SCHEMA
},
type: "object",
properties: {
...BASE_SCHEMA.properties,
overrides: {
type: "object",
properties: {
interface: {
$ref: "#/items/0/$defs/delimiterConfig"
},
typeLiteral: {
$ref: "#/items/0/$defs/delimiterConfig"
}
},
additionalProperties: false
},
multilineDetection: {
type: "string",
enum: ["brackets", "last-member"]
}
},
additionalProperties: false
}
]
},
defaultOptions: [
{
multiline: {
delimiter: "semi",
requireLast: true
},
singleline: {
delimiter: "semi",
requireLast: false
},
multilineDetection: "brackets"
}
],
create(context, [options]) {
const sourceCode = context.sourceCode;
const baseOptions = options;
const overrides = baseOptions.overrides ?? {};
const interfaceOptions = utils.deepMerge(
baseOptions,
overrides.interface
);
const typeLiteralOptions = utils.deepMerge(
baseOptions,
overrides.typeLiteral
);
function checkLastToken(member, opts, isLast) {
function getOption(type) {
if (isLast && !opts.requireLast) {
return type === "none";
}
return opts.delimiter === type;
}
let messageId = null;
let missingDelimiter = false;
const lastToken = sourceCode.getLastToken(member, {
includeComments: false
});
if (!lastToken)
return;
const commentsAfterLastToken = sourceCode.getCommentsAfter(lastToken).pop();
const sourceCodeLines = sourceCode.getLines();
const lastTokenLine = sourceCodeLines[lastToken?.loc.start.line - 1];
const optsSemi = getOption("semi");
const optsComma = getOption("comma");
const optsNone = getOption("none");
if (lastToken.value === ";") {
if (optsComma) {
messageId = "expectedComma";
} else if (optsNone) {
missingDelimiter = true;
messageId = "unexpectedSemi";
}
} else if (lastToken.value === ",") {
if (optsSemi) {
messageId = "expectedSemi";
} else if (optsNone) {
missingDelimiter = true;
messageId = "unexpectedComma";
}
} else {
if (optsSemi) {
missingDelimiter = true;
messageId = "expectedSemi";
} else if (optsComma) {
missingDelimiter = true;
messageId = "expectedComma";
}
}
if (messageId) {
context.report({
node: lastToken,
loc: {
start: {
line: lastToken.loc.end.line,
column: lastToken.loc.end.column
},
end: {
line: lastToken.loc.end.line,
column: lastToken.loc.end.column
}
},
messageId,
fix: makeFixFunction({
optsNone,
optsSemi,
lastToken,
commentsAfterLastToken,
missingDelimiter,
lastTokenLine,
isSingleLine: opts.type === "single-line"
})
});
}
}
function checkMemberSeparatorStyle(node) {
const members = node.type === utils$1.AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members;
let isSingleLine = node.loc.start.line === node.loc.end.line;
if (options.multilineDetection === "last-member" && !isSingleLine && members.length > 0) {
const lastMember = members[members.length - 1];
if (lastMember.loc.end.line === node.loc.end.line)
isSingleLine = true;
}
const typeOpts = node.type === utils$1.AST_NODE_TYPES.TSInterfaceBody ? interfaceOptions : typeLiteralOptions;
const opts = isSingleLine ? { ...typeOpts.singleline, type: "single-line" } : { ...typeOpts.multiline, type: "multi-line" };
members.forEach((member, index) => {
checkLastToken(member, opts ?? {}, index === members.length - 1);
});
}
return {
TSInterfaceBody: checkMemberSeparatorStyle,
TSTypeLiteral: checkMemberSeparatorStyle
};
}
});
module.exports = memberDelimiterStyle;