- 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."
235 lines
6.8 KiB
JavaScript
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;
|